从ViewPager中检索一个片段

我使用一个ViewPager与一个FragmentStatePagerAdapter来承载三个不同的片段:

  • [片段1]
  • [Fragment2]
  • [Fragment3]

当我想从Fragment1中的ViewPager获取FragmentActivity

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

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

这个解决scheme怎么样,覆盖你的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); } } 

在处理可用的碎片时,这似乎适用于我。 尚未实例化的碎片将在调用getRegisteredFragment时返回null。 但我一直在使用这大部分来获取当前FragmentViewPageradapater.getRegisteredFragment(viewPager.getCurrentItem()) ,这将不会返回null

我不知道这个解决scheme的其他缺点。 如果有的话,我想知道。

为了从ViewPager中抓取碎片,在这里和其他相关的SO线程/博客上有很多答案。 我所看到的每个人都是破碎的,而且他们似乎一般都属于下面列出的两种types之一。 还有一些其他有效的解决scheme,如果你只想抓取当前的片段,就像这个线程上的其他答案一样。

如果使用FragmentPagerAdapter请看下面。 如果使用FragmentStatePagerAdapter它值得看这个 。 在FragmentStateAdapter中抓取不是当前索引的索引不如它的本质那样有用,这些索引将被彻底拆除,从而不在视图边界之外。

不安定path

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

  • 通常使用SparseArrayMap
  • 我没有看到许多生命周期事件的例子,所以这个解决scheme是脆弱的。 由于只有在ViewPager.setOffscreenPageLimit(x)第一次页面被滚动到(或者如果您的ViewPager.setOffscreenPageLimit(x) > 0时才被获取), getItem被调用时,如果托pipeActivity / FragmentSpaseArray或重新启动,那么内部SpaseArray将被删除自定义的FragmentPagerActivity被重新创build,但后台的ViewPagers内部片段将被重新创build,并且getItem 不会被任何索引调用,所以从索引获取片段的能力将永远丢失。 你可以解决这个通过保存和恢复这些片段引用通过FragmentManager.getFragment()putFragment但这开始变得混乱恕我直言。

错误:构build您自己的标签ID,匹配FragmentPagerAdapter引用的内容并使用它从FragmentManager检索页面FragmentManager

  • 这更好,因为它解决了第一个内部数组解决scheme中的丢失碎片引用问题,但正如在上面和网上其他地方的答案中所指出的那样 – 它感觉像ViewPager内部的一个私有方法ViewPager ,可随时更改或任何操作系统版本。

这个解决scheme重新创build的方法是

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

一个HAPPY PATH: ViewPager.instantiateItem()

类似的getItem()方法在上面,但是在非生命周期中断的情况下,是挂钩到instantiateItem()而不是getItem()因为每当创build/访问索引时都会调用前者。 看到这个答案

一个HAPPYpath:构build你自己的FragmentViewPager

从最新的支持库的源头构build您自己的FragmentViewPager类,并更改内部使用的方法来生成片段标签。 你可以用下面的replace它。 这样做的好处是,你知道标签的创build永远不会改变,而且你不依赖私有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还没有被调用的结果可能为null该页面,即尚未创build。

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

这是一个简单的解决scheme,可以解决networking上其他两种解决scheme中的问题

将下一个方法添加到您的FragmentPagerAdapter:

 public Fragment getActiveFragment(ViewPager container, int position) { String name = makeFragmentName(container.getId(), position); return mFragmentManager.findFragmentByTag(name); } private static String makeFragmentName(int viewId, int index) { return "android:switcher:" + viewId + ":" + index; } 

getActiveFragment(0)必须工作。

这里是解决scheme实施到ViewPager https://gist.github.com/jacek-marchwicki/d6320ba9a910c514424d 。 如果失败,你会看到很好的崩溃日志。

另一个简单的方法

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

我知道这有几个答案,但也许这将有助于某人。 当我需要从ViewPager获取Fragment时,我使用了一个相对简单的解决scheme。 在持有ViewPagerActivityFragment ,您可以使用此代码循环访问每个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. } } 
  • 如果你知道你的FragmentViewPager中的ViewPager ,你可以调用getItem(knownPosition)

  • 如果你不知道你的FragmentViewPager中的ViewPager ,你可以让你的孩子Fragments使用像getUniqueId()这样的方法来实现一个接口,并用它来区分它们。 或者你可以遍历所有Fragments并检查类的types,比如if(viewPagerFragment instanceof FragmentClassYouWant)

编辑!

我发现,当每个Fragment需要首次被创build时, getItem只被一个FragmentPagerAdapter调用,之后,看起来Fragments被使用FragmentManager回收。 这样, FragmentPagerAdapter许多实现在getItem创build新的 Fragment 。 使用我上面的方法,这意味着我们将在每次调用getItem时创build新的Fragment因为我们遍历FragmentPagerAdapter中的所有项目。 由于这个原因,我find了一个更好的方法,使用FragmentManager来获取每个Fragment (使用接受的答案)。 这是一个更完整的解决scheme,一直在为我工作。

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

对于我的情况,上述解决scheme都没有工作。

不过,由于我在片段中使用了儿童片段pipe理器,因此使用了以下内容:

Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());

这只有在pipe理器中的片段对应于viewpager项目时才有效。

这是基于上面的Steven的回答。 这将返回已经附加到父活动的片段的实际实例。

 FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter(); for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) { Fragment viewPagerFragment = (Fragment) mViewPager.getAdapter().instantiateItem(mViewPager, i); if(viewPagerFragment != null && viewPagerFragment.isAdded()) { if (viewPagerFragment instanceof FragmentOne){ FragmentOne oneFragment = (FragmentOne) viewPagerFragment; if (oneFragment != null){ oneFragment.update(); // your custom method } } else if (viewPagerFragment instanceof FragmentTwo){ FragmentTwo twoFragment = (FragmentTwo) viewPagerFragment; if (twoFragment != null){ twoFragment.update(); // your custom method } } } } 

为了从ViewPager获得当前可见的片段 。 我正在使用这个简单的声明,它工作正常。

  public Fragment getFragmentFromViewpager() { return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem()))); } 

我找不到一个简单,干净的方法来做到这一点。 但是,ViewPager小部件只是托pipe碎片的另一个ViewGroup。 ViewPager将这些片段作为直接的孩子。 所以你可以迭代它们(使用.getChildCount()和.getChildAt()),看看你正在寻找的片段实例当前是否加载到ViewPager中并获取对它的引用。 例如,您可以使用一些静态唯一ID字段来区分碎片。

请注意,ViewPager可能没有加载你正在寻找的片段,因为它是一个像ListView这样的虚拟化容器。

FragmentPagerAdapter是片段的工厂。 如果仍然在内存中find一个基于它的位置的片段,使用这个:

 public Fragment findFragmentByPosition(int position) { FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter(); return getSupportFragmentManager().findFragmentByTag( "android:switcher:" + getViewPager().getId() + ":" + fragmentPagerAdapter.getItemId(position)); } 

v4支持api的示例代码。

我通过首先制作所有要使用的片段( List<Fragment> fragments; )的List<Fragment> fragments; ,然后将它们添加到分页器中,以便更容易地处理当前查看的片段。

所以:

 @Override onCreate(){ //initialise the list of fragments fragments = new Vector<Fragment>(); //fill up the list with out fragments fragments.add(Fragment.instantiate(this, MainFragment.class.getName())); fragments.add(Fragment.instantiate(this, MenuFragment.class.getName())); fragments.add(Fragment.instantiate(this, StoresFragment.class.getName())); fragments.add(Fragment.instantiate(this, AboutFragment.class.getName())); fragments.add(Fragment.instantiate(this, ContactFragment.class.getName())); //Set up the pager pager = (ViewPager)findViewById(R.id.pager); pager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments)); pager.setOffscreenPageLimit(4); } 

那么这可以被称为:

 public Fragment getFragment(ViewPager pager){ Fragment theFragment = fragments.get(pager.getCurrentItem()); return theFragment; } 

那么我可以把它放在if语句中,只有在正确的片段中才会运行

 Fragment tempFragment = getFragment(); if(tempFragment == MyFragmentNo2.class){ MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment; //then you can do whatever with the fragment theFrag.costomFunction(); } 

但多数民众赞成只是我的黑客和斜杠的方法,但它为我工作,我用它做相关的变化当前显示的片段,当后退button被按下。

不需要在稍后阶段调用getItem()或其他方法来获取ViewPager托pipe的Fragment的引用。 如果你想更新Fragment一些数据,那么使用这种方法: dynamic更新ViewPager?

关键是在Adaper设置新的数据,并调用notifyDataSetChanged() ,然后调用getItemPosition() ,传递给你一个Fragment的引用并给你一个更新它的机会。 所有其他的方式要求你继续参考自己或其他黑客这是不是一个好的解决scheme。

 @Override public int getItemPosition(Object object) { if (object instanceof UpdateableFragment) { ((UpdateableFragment) object).update(xyzData); } //don't return POSITION_NONE, avoid fragment recreation. return super.getItemPosition(object); } 

最好的解决scheme是使用我们在CodePath中创build的名为SmartFragmentStatePagerAdapter的扩展。 遵循该指南,这使得从ViewPager中检索片段和当前选定的片段变得容易得多。 它还能更好地pipe理embedded在适配器中的片段的内存。

必须将FragmentPagerAdapter扩展到您的ViewPager适配器类中。
如果您使用FragmentStatePagerAdapter那么您将无法通过其IDfind您的Fragment

 public static String makeFragmentName(int viewPagerId, int index) { return "android:switcher:" + viewPagerId + ":" + index; } 

如何使用这种方法: –

 Fragment mFragment = ((FragmentActivity) getContext()).getSupportFragmentManager().findFragmentByTag( AppMethodUtils.makeFragmentName(mViewPager.getId(), i) ); InterestViewFragment newFragment = (InterestViewFragment) mFragment; 

最简单和最简洁的方式。 如果ViewPager中的所有片段都是不同的类,则可以检索并区分它们,如下所示:

 public class MyActivity extends Activity { @Override public void onAttachFragment(Fragment fragment) { super.onAttachFragment(fragment); if (fragment.getClass() == MyFragment.class) { mMyFragment = (MyFragment) fragment; } } } 

我用一种不同的方法实现了这个简单的方法。

我的自定义FragmentAdapter.getItem方法返回不是新的MyFragment(),而是在FragmentAdapter构造函数中创build的MyFragment的实例。

在我的活动中,我从适配器中得到了片段,检查它是否是instanceOf所需的片段,然后进行投射并使用所需的方法。

 Fragment yourFragment = yourviewpageradapter.getItem(int index); 

index是在你添加fragment1适配器中的片段的位置,所以retreive fragment1index传递为0等等

在/values/integers.xml中创build整数资源ID

 <integer name="page1">1</integer> <integer name="page2">2</integer> <integer name="page3">3</integer> 

然后在PagerAdapter getItem函数中:

 public Fragment getItem(int position) { Fragment fragment = null; if (position == 0) { fragment = FragmentOne.newInstance(); mViewPager.setTag(R.integer.page1,fragment); } else if (position == 1) { fragment = FragmentTwo.newInstance(); mViewPager.setTag(R.integer.page2,fragment); } else if (position == 2) { fragment = FragmentThree.newInstance(); mViewPager.setTag(R.integer.page3,fragment); } return fragment; } 

然后在活动中写这个函数得到片段引用:

 private Fragment getFragmentByPosition(int position) { Fragment fragment = null; switch (position) { case 0: fragment = (Fragment) mViewPager.getTag(R.integer.page1); break; case 1: fragment = (Fragment) mViewPager.getTag(R.integer.page2); break; case 2: fragment = (Fragment) mViewPager.getTag(R.integer.page3); break; } return fragment; } 

通过调用上述函数获取片段引用,然后将其转换为您的自定义片段:

 Fragment fragment = getFragmentByPosition(position); if (fragment != null) { FragmentOne fragmentOne = (FragmentOne) fragment; } 

简单的方法来迭代片段pipe理器中的片段。 查找具有节位置参数的viewpager,放置在公共静态PlaceholderFragment newInstance(int sectionNumber)中

 public PlaceholderFragment getFragmentByPosition(Integer pos){ for(Fragment f:getChildFragmentManager().getFragments()){ if(f.getId()==R.id.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) { return (PlaceholderFragment) f; } } return null; } 

在片段

 public int getArgument(){ return mPage; { public void update(){ } 

在FragmentActivity

 List<Fragment> fragments = getSupportFragmentManager().getFragments(); for(Fragment f:fragments){ if((f instanceof PageFragment)&&(!f.isDetached())){ PageFragment pf = (PageFragment)f; if(pf.getArgument()==pager.getCurrentItem())pf.update(); } } 

好的适配器FragmentStatePagerAdapter我资助一个解决scheme:

在你的FragmentActivity中:

 ActionBar mActionBar = getSupportActionBar(); mActionBar.addTab(mActionBar.newTab().setText("TAB1").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment1.class.getName()))); mActionBar.addTab(mActionBar.newTab().setText("TAB2").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment2.class.getName()))); mActionBar.addTab(mActionBar.newTab().setText("TAB3").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment3.class.getName()))); viewPager = (STViewPager) super.findViewById(R.id.viewpager); mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), mActionBar); viewPager.setAdapter(this.mPagerAdapter); 

并在你的类中创build一个方法FragmentActivity – 所以这个方法可以让你访问你的Fragment,你只需要给它一个你想要的片段的位置:

 public Fragment getActiveFragment(int position) { String name = MyPagerAdapter.makeFragmentName(position); return getSupportFragmentManager().findFragmentByTag(name); } 

在你的适配器中:

 public class MyPagerAdapter extends FragmentStatePagerAdapter { private final ActionBar actionBar; private final FragmentManager fragmentManager; public MyPagerAdapter(FragmentManager fragmentManager, com.actionbarsherlock.app.ActionBarActionBar mActionBar) {super(fragmentManager); this.actionBar = mActionBar; this.fragmentManager = fragmentManager; } @Override public Fragment getItem(int position) { getSupportFragmentManager().beginTransaction().add(mTchatDetailsFragment, makeFragmentName(position)).commit(); return (Fragment)this.actionBar.getTabAt(position); } @Override public int getCount() { return this.actionBar.getTabCount(); } @Override public CharSequence getPageTitle(int position) { return this.actionBar.getTabAt(position).getText(); } private static String makeFragmentName(int viewId, int index) { return "android:fragment:" + index; } } 

这个简单的解决scheme对我来说很好。

YourFragment fragment = (YourFragment)getSupportFragmentManager().getFragments().get(0);

(如果你知道你的片段在列表中的位置,那么上面的解决scheme将工作)