我使用一个ViewPager和一个FragmentStatePagerAdapter来托管三个不同的片段:

(Fragment1) (Fragment2) (Fragment3)

当我想从FragmentActivity中的ViewPager中获取Fragment1时。

问题是什么,我该如何解决它?


当前回答

我知道这有几个答案,但也许这能帮助到一些人。当我需要从ViewPager中获取片段时,我使用了一个相对简单的解决方案。在持有ViewPager的Activity或Fragment中,你可以使用这段代码循环遍历它持有的每个Fragment。

FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
    Fragment viewPagerFragment = fragmentPagerAdapter.getItem(i);
    if(viewPagerFragment != null) {
        // Do something with your Fragment
        // Check viewPagerFragment.isResumed() if you intend on interacting with any views.
    }
}

如果你知道片段在ViewPager中的位置,你可以调用getItem(knownPosition)。 如果你不知道Fragment在ViewPager中的位置,你可以用getUniqueId()这样的方法让你的子Fragment实现一个接口,并使用它来区分它们。或者你可以循环遍历所有片段并检查类类型,例如if(viewPagerFragment instanceof FragmentClassYouWant)

! !编辑! !

I have discovered that getItem only gets called by a FragmentPagerAdapter when each Fragment needs to be created the first time, after that, it appears the the Fragments are recycled using the FragmentManager. This way, many implementations of FragmentPagerAdapter create new Fragments in getItem. Using my above method, this means we will create new Fragments each time getItem is called as we go through all the items in the FragmentPagerAdapter. Due to this, I have found a better approach, using the FragmentManager to get each Fragment instead (using the accepted answer). This is a more complete solution, and has been working well for me.

FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
    String name = makeFragmentName(mViewPager.getId(), i);
    Fragment viewPagerFragment = getChildFragmentManager().findFragmentByTag(name);
    // OR Fragment viewPagerFragment = getFragmentManager().findFragmentByTag(name);
    if(viewPagerFragment != null) {

        // Do something with your Fragment
        if (viewPagerFragment.isResumed()) {
            // Interact with any views/data that must be alive
        }
        else {
            // Flag something for update later, when this viewPagerFragment
            // returns to onResume
        }
    }
}

你需要这个方法。

private static String makeFragmentName(int viewId, int position) {
    return "android:switcher:" + viewId + ":" + position;
}

其他回答

对于从ViewPager中抓取片段,在这里和其他相关的SO线程/博客上有很多答案。我见过的每个人都很坏,他们通常属于下面列出的两种类型之一。如果你只想抓取当前片段,还有一些其他有效的解决方案,比如这个线程上的其他答案。

如果使用FragmentPagerAdapter参见下面。如果使用FragmentStatePagerAdapter,值得一看。抓取不是FragmentStateAdapter中当前索引的索引并没有那么有用,因为从本质上讲,这些索引将完全被删除,离开视图/ offScreenLimit边界。

不幸的道路

错误:维护自己的内部片段列表,当FragmentPagerAdapter.getItem()被调用时添加

Usually using a SparseArray or Map Not one of the many examples I have seen accounts for lifecycle events so this solution is fragile. As getItem is only called the first time a page is scrolled to (or obtained if your ViewPager.setOffscreenPageLimit(x) > 0) in the ViewPager, if the hosting Activity / Fragment is killed or restarted then the internal SpaseArray will be wiped out when the custom FragmentPagerActivity is recreated, but behind the scenes the ViewPagers internal fragments will be recreated, and getItem will NOT be called for any of the indexes, so the ability to get a fragment from index will be lost forever. You can account for this by saving out and restoring these fragment references via FragmentManager.getFragment() and putFragment but this starts to get messy IMHO.

错误:构造自己的标签id,匹配在FragmentPagerAdapter中使用的内容,并使用它从FragmentManager中检索页面片段

这在处理第一个内部数组解决方案中丢失片段引用的问题上是更好的,但正如上面和网上其他地方的答案中正确指出的那样——它是ViewPager内部的私有方法,可以在任何时间或任何OS版本中更改,这让人感觉很不舒服。

为这个解决方案重新创建的方法是

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

viewpage . instantiateitem ()

与上面的getItem()类似但不破坏生命周期的方法是钩子到instantiateItem()而不是getItem(),因为前者将在每次创建/访问索引时调用。请看这个答案

快乐之路:构建你自己的FragmentViewPager

从最新支持库的源代码中构造您自己的FragmentViewPager类,并更改用于生成片段标记的内部方法。你可以用下面的替换。这样做的好处是,你知道标签的创建永远不会改变,而且你不依赖于私有api /方法,这总是很危险的。

/**
 * @param containerViewId the ViewPager this adapter is being supplied to
 * @param id pass in getItemId(position) as this is whats used internally in this class
 * @return the tag used for this pages fragment
 */
public static String makeFragmentName(int containerViewId, long id) {
    return "android:switcher:" + containerViewId + ":" + id;
}

然后,正如文档所说,当你想抓取一个片段用于索引只是调用像这样的方法(你可以把自定义FragmentPagerAdapter或一个子类)意识到,如果getItem还没有被调用的页面,即它还没有被创建,结果可能是空的。

/**
 * @return may return null if the fragment has not been instantiated yet for that position - this depends on if the fragment has been viewed
 * yet OR is a sibling covered by {@link android.support.v4.view.ViewPager#setOffscreenPageLimit(int)}. Can use this to call methods on
 * the current positions fragment.
 */
public @Nullable Fragment getFragmentForPosition(int position)
{
    String tag = makeFragmentName(mViewPager.getId(), getItemId(position));
    Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
    return fragment;
}

这是一个简单的解决方案,解决了其他两个解决方案在网络上随处可见的问题

主要答案依赖于框架生成的名称。如果这种情况发生变化,那么它将不再起作用。

这个解决方案怎么样,重写你的Fragment(State)PagerAdapter的instantiateItem()和destroyItem():

public class MyPagerAdapter extends FragmentStatePagerAdapter {
    SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();

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

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

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(...); 
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredFragments.put(position, fragment);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        registeredFragments.remove(position);
        super.destroyItem(container, position, object);
    }

    public Fragment getRegisteredFragment(int position) {
        return registeredFragments.get(position);
    }
}

在处理可用的Fragments时,这似乎对我有用。尚未实例化的片段在调用getRegisteredFragment时将返回null。但我一直在使用这个主要是为了获得当前片段的ViewPager: adapter . getregisteredfragment (viewpage . getcurrentitem()),这不会返回null。

我不知道这个解决方案还有什么其他缺点。如果有的话,我想知道。

另一个简单的解决方案:

    public class MyPagerAdapter extends FragmentPagerAdapter {
        private Fragment mCurrentFragment;

        public Fragment getCurrentFragment() {
            return mCurrentFragment;
        }
//...    
        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            if (getCurrentFragment() != object) {
                mCurrentFragment = ((Fragment) object);
            }
            super.setPrimaryItem(container, position, object);
        }
    }
Fragment yourFragment = yourviewpageradapter.getItem(int index);

Index是fragment在适配器中的位置,比如你先添加fragment1,所以检索fragment1时将Index传递为0,以此类推

我知道这有几个答案,但也许这能帮助到一些人。当我需要从ViewPager中获取片段时,我使用了一个相对简单的解决方案。在持有ViewPager的Activity或Fragment中,你可以使用这段代码循环遍历它持有的每个Fragment。

FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
    Fragment viewPagerFragment = fragmentPagerAdapter.getItem(i);
    if(viewPagerFragment != null) {
        // Do something with your Fragment
        // Check viewPagerFragment.isResumed() if you intend on interacting with any views.
    }
}

如果你知道片段在ViewPager中的位置,你可以调用getItem(knownPosition)。 如果你不知道Fragment在ViewPager中的位置,你可以用getUniqueId()这样的方法让你的子Fragment实现一个接口,并使用它来区分它们。或者你可以循环遍历所有片段并检查类类型,例如if(viewPagerFragment instanceof FragmentClassYouWant)

! !编辑! !

I have discovered that getItem only gets called by a FragmentPagerAdapter when each Fragment needs to be created the first time, after that, it appears the the Fragments are recycled using the FragmentManager. This way, many implementations of FragmentPagerAdapter create new Fragments in getItem. Using my above method, this means we will create new Fragments each time getItem is called as we go through all the items in the FragmentPagerAdapter. Due to this, I have found a better approach, using the FragmentManager to get each Fragment instead (using the accepted answer). This is a more complete solution, and has been working well for me.

FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
    String name = makeFragmentName(mViewPager.getId(), i);
    Fragment viewPagerFragment = getChildFragmentManager().findFragmentByTag(name);
    // OR Fragment viewPagerFragment = getFragmentManager().findFragmentByTag(name);
    if(viewPagerFragment != null) {

        // Do something with your Fragment
        if (viewPagerFragment.isResumed()) {
            // Interact with any views/data that must be alive
        }
        else {
            // Flag something for update later, when this viewPagerFragment
            // returns to onResume
        }
    }
}

你需要这个方法。

private static String makeFragmentName(int viewId, int position) {
    return "android:switcher:" + viewId + ":" + position;
}