从ViewPager中检索一个片段

我使用ViewPagerFragmentStatePagerAdapter来承载三个不同的片段:

  • (Fragment1)
  • (Fragment2)
  • (Fragment3)

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

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

154776 次浏览

我找不到一个简单干净的方法来做这件事。然而,ViewPager小部件只是另一个ViewGroup,它承载着你的片段。ViewPager将这些片段作为直接子片段。所以你可以遍历它们(使用. getchildcount()和. getchildat()),看看你正在寻找的片段实例当前是否加载到ViewPager中,并获得对它的引用。例如,你可以使用一些静态的唯一ID字段来区分片段。

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

为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)必须工作。

下面是实现到ViewPager https://gist.github.com/jacek-marchwicki/d6320ba9a910c514424d中的解决方案。如果出现故障,你会看到良好的崩溃日志。

我知道这有几个答案,但也许这能帮助到一些人。当我需要从ViewPager中获取Fragment时,我使用了一个相对简单的解决方案。在包含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中的位置,你可以直接调用getItem(knownPosition)

  • 如果你不知道你的FragmentViewPager中的位置,你可以让你的孩子Fragments用类似getUniqueId()的方法实现一个接口,并用它来区分它们。或者你可以循环遍历所有Fragments并检查类类型,例如if(viewPagerFragment instanceof FragmentClassYouWant)

!!! 编辑! !

我发现getItem只在每个Fragment需要创建FragmentPagerAdapter3时被FragmentPagerAdapter调用,之后,Fragments似乎使用FragmentManager被回收。这样,许多FragmentPagerAdapter的实现在getItem中创建了FragmentPagerAdapter4 Fragments。使用上面的方法,这意味着在遍历FragmentPagerAdapter中的所有项时,每次调用getItem都将创建新的__abc2。因此,我找到了一个更好的方法,使用FragmentManager来获得每个Fragment(使用公认的答案)。这是一个更完整的解决方案,对我来说效果很好。

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

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

这个解决方案如何,重写你的Fragment(State)PagerAdapterinstantiateItem()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: adapater.getRegisteredFragment(viewPager.getCurrentItem())中获得当前的Fragment,这不会返回null

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

好的适配器FragmentStatePagerAdapter我基金一个解决方案:

在你的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);

在你的类FragmentActivity中创建一个方法——这样你就可以访问你的Fragment,你只需要给它你想要的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;
}


}

FragmentPagerAdapter是片段的工厂。如果一个片段仍然在内存中,使用下面的方法查找它的位置:

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

v4支持api的示例代码。

我处理它首先做一个列表的所有片段(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();
}

但这只是我的hack和slash方法,但它为我工作,我用它做相关的改变,我目前显示的片段,当后退按钮被按下。

需要在后面的阶段调用getItem()或其他一些方法来获得托管在ViewPager中的Fragment的引用。如果你想在Fragment中更新一些数据,那么使用这个方法:动态更新ViewPager ?

关键是在Adaper中设置新数据并调用notifyDataSetChanged(),而notifyDataSetChanged()将调用getItemPosition(),传递给你Fragment的引用并给你更新它的机会。所有其他方法都需要你参考自己或其他方法,这不是一个好的解决方案。

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

另一个简单的解决方案:

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

最好的解决方案是使用我们在CodePath创建的名为SmartFragmentStatePagerAdapter的扩展。按照这个指南,这使得从ViewPager中检索片段和当前选择的片段变得非常容易。它还可以更好地管理嵌入到适配器中的片段的内存。

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

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

不幸的道路

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

  • 通常使用SparseArrayMap
  • 我所见过的许多例子中没有一个涉及生命周期事件,因此这个解决方案是脆弱的。getItem只是称为第一次滚动页面(或者如果你获得ViewPager.setOffscreenPageLimit(x) > 0) ViewPager,如果托管Activity / Fragment杀死或重新启动内部SpaseArray将被消灭时自定义FragmentPagerActivity重建,但在幕后ViewPagers内部碎片将重新创建,和getItem 将呼吁任何索引,所以能够得到一个片段来自索引将永远失去了。你可以通过保存并通过FragmentManager.getFragment()putFragment来恢复这些片段引用,但恕我直言,这开始变得混乱。

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

  • 这样做更好,因为它处理了第一个内部数组解决方案中丢失片段引用的问题,但正如上面和网上其他地方的答案中正确指出的那样——它是ViewPager内部的私人方法,可能在任何时间或任何OS版本中改变。

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

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

一个愉快的路径:__abc0

类似于上面的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;
}

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

最简单、最简洁的方法。如果你在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构造函数中创建的MyFragment实例。

在我的活动中,我然后从适配器获得片段,检查它是否是需要的片段的instanceOf,然后强制转换和使用所需的方法。

在我的案例中,上述解决方案都不起作用。

然而,由于我在一个片段中使用了子片段管理器,下面使用了:

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

只有当管理器中的片段与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
}
}
}
}
Fragment yourFragment = yourviewpageradapter.getItem(int index);

index是片段在适配器中的位置,就像你先添加fragment1一样,因此检索fragment1,将index传递为0,以此类推

在/values/integers.xml中创建整数资源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;
}

然后在activity中写这个函数来获取片段引用:

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

在片段管理器中迭代片段的简单方法。找到viewpager,它有section position参数,放置在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;
}

为了得到来自ViewPager的当前可见片段。我用了这个简单的语句,效果很好。

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

在片段

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

必须将FragmentPagerAdapter扩展到你的ViewPager适配器类 如果你使用FragmentStatePagerAdapter,那么你将无法通过ID

找到你的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;

在TabLayout中有多个标签片段。你可以通过标签找到片段,使用片段的索引。

例如Fragment1的索引是0,所以在findFragmentByTag()方法中,传递Viewpager的标记。使用fragmentTransaction后可以添加、替换fragment。

String tag = "android:switcher:" + R.id.viewPager + ":" + 0; Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);

嘿,我已经回答了这个问题在这里。基本上,你需要重写

实例化对象(ViewGroup容器,int位置)

FragmentStatePagerAdapter的方法。