从ViewPager中检索片段

Qun*_*Qin 240 android android-fragments android-viewpager

我使用的是ViewPager连同FragmentStatePagerAdapter主办三个不同的片段:

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

当我想Fragment1ViewPagerFragmentActivity.

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

Str*_*ton 499

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

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在处理可用的碎片时,这似乎对我有用.尚未实例化的片段在调用时将返回null getRegisteredFragment.但我一直在使用这主要是为了获取当前Fragment出的ViewPager:adapater.getRegisteredFragment(viewPager.getCurrentItem())这将不会返回null.

我不知道这个解决方案的任何其他缺点.如果有的话,我想知道.

  • 这是我能想到的最佳解决方案.唯一的建议是将'Fragment`包装在`WeakReference`中,以保证不会阻止被破坏的片段被垃圾收集?看起来似乎是正确的事...... (40认同)
  • 如果我没有弄错(并且你应该检查一下),则不会调用getItem方法 - 正如您所知 - 但是为旋转后框架正在恢复的每个Fragment调用了instantiateItem方法. (10认同)
  • 当`MyPagerAdapter`因生命周期(即旋转)而被破坏时会发生什么,"registerdFragments"不会丢失?使用`MyPagerAdapter`的`Activity` /`Fragment`是否必须将它保存在`onSaveInstanceState`中,然后需要用新的`FragmentManager` ref更新它? (7认同)
  • 是的,我已经验证了`getItem`在旋转时没有被再次调用(对于任何已经创建的碎片),因为`FragmentManager`恢复了它在寻呼机中保存的`Fragments`的状态.如果在恢复每个"片段"时调用`instantiateItem`,那么这个解决方案实际上比我的或者接受的答案更安全且更具未来证明**.我会考虑自己尝试一下. (6认同)
  • @Dori我没有打破它.我的解决方案是不使用'getItem()'调用的结果.相反,它使用从'instantiateItem()'调用存储的结果.并且片段的生命周期没有被破坏,因为存储的片段将在'destroyItem()'调用中从稀疏数组中删除. (6认同)
  • 如何使用此适配器添加片段? (2认同)

Dor*_*ori 85

为了从ViewPager中抓取片段,这里和其他相关的SO线程/博客上有很多答案.我见过的每个人都被打破了,他们通常似乎属于下面列出的两种类型之一.还有一些其他的解决方案有效,如果你只想要抓住当前片段,像这样在此线程的其他答案.

如果使用FragmentPagerAdapter见下文.如果用FragmentStatePagerAdapter它值得看这个.在FragmentStateAdapter中抓取不是当前索引的索引并不像它的性质那样有用,这些索引将完全拆除掉在offScreenLimit边界之外.

不适当的路径

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

  • 通常使用SparseArrayMap
  • 我见过很多关于生命周期事件的例子,所以这个解决方案很脆弱.由于getItem只是第一次将页面滚动到(或者如果你的ViewPager.setOffscreenPageLimit(x)> 0 获得)ViewPager,如果托管Activity/ Fragment被杀死或重新启动,那么SpaseArray当重新创建自定义FragmentPagerActivity时内部将被清除,但是在幕后ViewPagers内部片段将被重新创建,并且getItem被要求任何的指标,所以从指数得到片段的能力将永远消失.您可以通过保存和恢复这些片段引用来解决这个问题FragmentManager.getFragment(), putFragment但这开始变得混乱恕我直言.

错误:构建您自己的标签ID,匹配引擎盖下使用的标签ID,FragmentPagerAdapter并使用它来检索页面碎片FragmentManager

  • 这是更好的,因为它应对第一个内部数组解决方案中的丢失片段引用问题,但正如上面的答案和网络上的其他地方正确指出的那样 - 它感觉很hacky,因为它内部的私有方法ViewPager可以随时更改或任何操作系统版本.

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

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

一条快乐的道路: ViewPager.instantiateItem()

对于getItem()上面但非生命周期中断的类似方法是,这是钩入instantiateItem()而不是getItem()因为每次创建/访问索引时都会调用前者.看到这个答案

快乐的道路:构建自己的道路 FragmentViewPager

FragmentViewPager最新支持lib源代码构造您自己的类,并更改内部使用的方法以生成片段标记.您可以使用以下内容替换它.这样做的好处是,您知道标签创建永远不会改变,并且您不依赖于私有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;
}
Run Code Online (Sandbox Code Playgroud)

然后正如文档所说,当你想要获取用于索引的片段时,只需调用类似于此方法的东西(可以放入自定义FragmentPagerAdapter或子类中),如果尚未调用getItem,则可以知道结果可能为null该页面即尚未创建.

/**
 * @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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这将有效,但它需要将ViewPager类复制到您自己的源代码中.依赖ViewPager的寻呼机适配器的'instantiateItem()'和'destroyItem()'将工作并将遵循片段的生命周期(当从保存的实例状态创建片段时,也会调用这两个方法).你是对的,依赖'getItem()'被调用并不是一个好主意,使用私有API也不是. (6认同)

kzo*_*tin 70

将以下方法添加到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;
}
Run Code Online (Sandbox Code Playgroud)

getActiveFragment(0)必须工作.

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

  • 我正在使用这种方法,但我相信它在我转移到API 17(?)时停止工作.我会推荐另一种方法,如果谷歌只是发布了一个API来从ViewPager中检索片段,我会更喜欢它. (12认同)
  • 由于某些API版本的makeFragmentName方法已更改为私有静态字符串makeFragmentName(int viewId,long id){return"android:switcher:"+ viewId +":"+ id; 所以getActiveFragment方法现在看起来应该是这样的(使用getItemId代替第二个参数的位置)public Fragment getActiveFragment(ViewPager container,int position){String name = makeFragmentName(container.getId(),getItemId(position)); return mFragmentManager.findFragmentByTag(name); } (2认同)

小智 38

另一个简单的解决

    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);
        }
    }
Run Code Online (Sandbox Code Playgroud)


Ste*_*yle 21

我知道这有几个答案,但也许这会对某人有所帮助.当我需要得到一个Fragment来自我的时候,我使用了一个相对简单的解决方案ViewPager.在您持有ActivityFragment持有时ViewPager,您可以使用此代码循环每个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.
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 如果你知道你的位置FragmentViewPager,你可以调用getItem(knownPosition).

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

!编辑!!!

我发现,getItem只有获得由被称为FragmentPagerAdapter每当Fragment需要创建的第一次,在此之后,它会出现在Fragments使用被回收FragmentManager.这样,FragmentPagerAdapter创建新的 Fragment s的许多实现getItem.使用我的上述方法,这意味着我们将Fragment在每次getItem调用时创建新的s,因为我们遍历了所有项目FragmentPagerAdapter.因此,我找到了一种更好的方法,使用它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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你需要这种方法.

private static String makeFragmentName(int viewId, int position) {
    return "android:switcher:" + viewId + ":" + position;
}
Run Code Online (Sandbox Code Playgroud)


小智 10

就我而言,上述解决方案均无效.

但是,由于我在Fragment中使用Child Fragment Manager,因此使用了以下内容:

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

这仅在Manager中的片段对应于viewpager项时才有效.


Xar*_*mer 7

为了从ViewPager获取当前的Visible片段.我正在使用这个简单的声明,它工作正常.

      public Fragment getFragmentFromViewpager()  
      {
         return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
      }
Run Code Online (Sandbox Code Playgroud)


Zso*_*agy 2

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

请注意,ViewPager 可能尚未加载您正在查找的片段,因为它是像 ListView 一样的虚拟化容器。


归档时间:

查看次数:

145380 次

最近记录:

7 年,5 月 前