首页 文章

从ViewPager中检索片段

提问于
浏览
225

我正在使用 ViewPagerFragmentStatePagerAdapter 来托管三个不同的片段:

  • [Fragment1]

  • [Fragment2]

  • [Fragment3]

当我想从 FragmentActivity 中的 ViewPager 获得 Fragment1 时 .

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

23 回答

  • 83

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

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

    在处理可用的碎片时,这似乎对我有用 . 尚未实例化的片段在调用 getRegisteredFragment 时将返回null . 但是我一直在使用这个来获取当前的 Fragment ViewPageradapater.getRegisteredFragment(viewPager.getCurrentItem()) 并且这不会返回 null .

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

  • 70

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

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

    不适当的路径

    Wrong: Maintain your own internal list of fragments, added to when FragmentPagerAdapter.getItem() is called

    • 通常使用 SparseArrayMap

    • 我见过生命周期事件的许多例子中没有一个,所以这个解决方案很脆弱 . 由于 getItem 仅在 ViewPager 中第一次滚动页面(或者如果 ViewPager.setOffscreenPageLimit(x) > 0获得)时被调用,如果主机 Activity / Fragment 被终止或重新启动,则在重新创建自定义FragmentPagerActivity时将清除内部 SpaseArray ,但在幕后,将重新创建ViewPagers内部片段,并且 getItem 将为任何索引调用 NOT ,因此从索引获取片段的能力将永远丢失 . 您可以通过 FragmentManager.getFragment()putFragment 保存并恢复这些片段引用来解决这个问题,但这开始变得混乱恕我直言 .

    Wrong: Construct your own tag id matching what is used under the hood in FragmentPagerAdapter 并使用此方法从 FragmentManager 检索页面碎片

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

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

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

    A HAPPY PATH:ViewPager.instantiateItem()

    A similar approach to getItem() above but non-lifecycle-breaking 这是挂钩到 instantiateItem() 而不是 getItem() ,因为每次创建/访问索引时都会调用前者 . 见this answer

    A HAPPY PATH:构建自己的FragmentViewPager

    source of the latest support lib构造您自己的 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,则可以知道结果 may be 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;
    }
    

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

  • 2

    将以下方法添加到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中实现的解决方案 . 如果出现故障,您将看到良好的崩溃日志 .

  • 2

    另一个简单的解决

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

    我知道这有几个答案,但也许这会对某人有所帮助 . 当我需要从 ViewPager 获得 Fragment 时,我使用了一个相对简单的解决方案 . 在 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.
        }
    }
    
    • 如果你知道 FragmentFragment 的位置,你可以调用 getItem(knownPosition) .

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

    !!! EDIT !!!

    我发现 getItem 仅在 FragmentPagerAdapter 需要创建 first time 时被 FragmentPagerAdapter 调用,之后,使用 FragmentManager 回收 Fragments . 这个在 getItem 中, FragmentPagerAdapter 创建 new Fragment 的许多实现方式 . 使用我的上述方法,这意味着我们将在每次 getItem 被调用时创建新的 Fragment ,因为我们遍历了 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
            }
        }
    }
    

    你需要这种方法 .

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

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

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

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

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

  • 38

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

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

    这是基于史蒂文上面的答案 . 这将返回已附加到父活动的片段的实际实例 .

    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
                    }
                }
            }
        }
    
  • 3

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

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

  • 0

    FragmentPagerAdapter是片段的工厂 . 要在仍然在内存中的情况下根据其位置查找片段,请使用以下命令:

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

    v4支持api的示例代码 .

  • 1

    我通过首先制作我将要使用的所有片段( 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();
    }
    

    但这只是我的黑客和斜线方法,但它对我有用,我用它做按下后退按钮时对我当前显示的片段进行相关更改 .

  • 2

    don't 需要在后期调用 getItem() 或其他一些方法来获取 ViewPager 中托管的 Fragment 的引用 . 如果要更新 Fragment 中的某些数据,请使用以下方法:Update ViewPager dynamically?

    关键是在 Adaper 中设置新数据并调用 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);
    }
    
  • 6

    必须将 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;
    
  • 10

    嘿,我已经回答了这个问题here . 基本上,你需要覆盖

    public Object instantiateItem(ViewGroup容器,int位置)

    FragmentStatePagerAdapter的方法 .

  • 2

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

  • -1

    最简单,最简洁的方式 . 如果 ViewPager 中的所有片段属于不同的类,您可以检索并区分它们如下:

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

    我用一种不同的方法实现了这一点 .

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

    在我的活动中,我从适配器获取片段,检查它是否是instanceOf需要片段,然后转换并使用所需的方法 .

  • 2

    在/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;
                        }
    
  • 0

    在片段管理器中迭代片段的简便方法 . 查找具有section位置参数的viewpager,放置在public static 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;
    }
    
  • 0

    在片段中

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

    在TabLayout中,Fragment有多个选项卡 . 你可以使用片段的索引通过Tag找到片段 .

    对于前者Fragment1的索引是0,所以在 findFragmentByTag() 方法中,传递Viewpager的标签 . 使用fragmentTransaction你可以添加,替换片段 .

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

  • 2

    好的适配器 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中创建一个方法 - 所以这个方法可以让你访问你的片段,你只需要给它你想要的片段的位置:

    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;
    }
    
    }
    
  • 0
    Fragment yourFragment = yourviewpageradapter.getItem(int index);
    

    index 是适配器中片段的位置,就像你先添加 fragment1 所以retreive fragment1 传递 index 为0等等休息

相关问题