使用FragmentPagerAdapter时如何获取现有片段

Ism*_*mic 96 android android-fragments fragmentpageradapter

我有问题使我的片段通过Activity使用FragmentPagerAdapter,作为帮助类来实现对标签的管理以及连接ViewPager关联的所有细节TabHost.我的实现FragmentPagerAdapter与Android示例项目Support4Demos提供的一样.

主要问题是FragmentManager当我没有Id或Tag时,如何从特定片段中获取特定片段?FragmentPagerAdapter正在创建片段并自动生成Id和标签.

Ton*_*han 187

问题摘要

注意:在这个答案中,我将引用FragmentPagerAdapter它的源代码.但一般的解决方案也应该适用FragmentStatePagerAdapter.

如果您正在阅读本文,您可能已经知道FragmentPagerAdapter/ FragmentStatePagerAdapter意在Fragments为您创建/ ViewPager但是在Activity重新创建时(无论是从设备轮换还是从系统中删除您的App以重新获得内存)这些Fragments都不会再次创建,而是他们的从中检索的实例FragmentManager.现在说你Activity需要获得这些参考来Fragments对它们进行工作.您没有创建idtag创建这些,Fragments因为FragmentPagerAdapter 在内部设置它们.所以问题是如何在没有这些信息的情况下获得对它们的引用......

当前解决方案的问题:依赖内部代码

我在这个和类似问题上看到的很多解决方案都依赖于Fragment通过调用FragmentManager.findFragmentByTag()和模仿内部创建的标记"android:switcher:" + viewId + ":" + id来获取对现有的引用:.这样做的问题在于你依赖于内部源代码,我们都知道并不能保证它们永远保持不变.Google的Android工程师可以轻松决定更改tag会破坏您的代码的结构,从而无法找到对现有代码的引用Fragments.

替代解决方案,不依赖于内部 tag

这是一个简单的例子,说明如何获取对Fragments返回的引用FragmentPagerAdapter,而不依赖于内部tagsFragments.关键是instantiateItem()在那里覆盖和保存引用而不是getItem().

public class SomeActivity extends Activity {
    private FragmentA m1stFragment;
    private FragmentB m2ndFragment;

    // other code in your Activity...

    private class CustomPagerAdapter extends FragmentPagerAdapter {
        // other code in your custom FragmentPagerAdapter...

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

        @Override
        public Fragment getItem(int position) {
            // Do NOT try to save references to the Fragments in getItem(),
            // because getItem() is not always called. If the Fragment
            // was already created then it will be retrieved from the FragmentManger
            // and not here (i.e. getItem() won't be called again).
            switch (position) {
                case 0:
                    return new FragmentA();
                case 1:
                    return new FragmentB();
                default:
                    // This should never happen. Always account for each position above
                    return null;
            }
        }

        // Here we can finally safely save a reference to the created
        // Fragment, no matter where it came from (either getItem() or
        // FragmentManger). Simply save the returned Fragment from
        // super.instantiateItem() into an appropriate reference depending
        // on the ViewPager position.
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
            // save the appropriate reference depending on position
            switch (position) {
                case 0:
                    m1stFragment = (FragmentA) createdFragment;
                    break;
                case 1:
                    m2ndFragment = (FragmentB) createdFragment;
                    break;
            }
            return createdFragment;
        }
    }

    public void someMethod() {
        // do work on the referenced Fragments, but first check if they
        // even exist yet, otherwise you'll get an NPE.

        if (m1stFragment != null) {
            // m1stFragment.doWork();
        }

        if (m2ndFragment != null) {
            // m2ndFragment.doSomeWorkToo();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者如果您更喜欢使用tags而不是使用类成员变量/引用,Fragments您也可以tags通过FragmentPagerAdapter相同的方式获取该集:注意:这不适用于FragmentStatePagerAdapter因为它tags在创建时没有设置Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
    // get the tags set by FragmentPagerAdapter
    switch (position) {
        case 0:
            String firstTag = createdFragment.getTag();
            break;
        case 1:
            String secondTag = createdFragment.getTag();
            break;
    }
    // ... save the tags somewhere so you can reference them later
    return createdFragment;
}
Run Code Online (Sandbox Code Playgroud)

请注意,此方法不依赖于模仿内部tag集合FragmentPagerAdapter,而是使用适当的API来检索它们.这种方式即使你tag未来版本的变化SupportLibrary仍然是安全的.


不要忘记,根据您的设计Activity,Fragments您正在尝试的工作可能存在,也可能不存在,因此您必须null在使用引用之前通过检查来解释这一点.

另外,如果您正在使用FragmentStatePagerAdapter,那么您不希望保留对您的硬引用,Fragments因为您可能有许多引用,并且硬引用会不必要地将它们保留在内存中.而是将Fragment引用保存在WeakReference变量而不是标准变量中.像这样:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
    // reference hasn't been cleared yet; do work...
}
Run Code Online (Sandbox Code Playgroud)

  • 这值得勾选!非常聪明和全面的方法来实现这一目标.你帮了我很多,谢谢你! (12认同)
  • @Zorpix你可以将创建片段存储在HashMap中:map.put(position,createdFragment); (3认同)
  • 这是一个非常好的解决方案,但是如果您不知道将传递多少片段,它似乎会失去有效性。 (2认同)
  • 起初这个解决方案看起来太复杂了,所以我跳过它.不过,我终于回到了它,因为其他答案并不令人满意.它并没有我想象的那么难. (2认同)

Ism*_*mic 80

我根据以下帖子找到了我的问题的答案:在fragmentpageradapter中重用片段

我学到的东西很少:

  1. getItem(int position)FragmentPagerAdapter这个方法实际上做的是相当误导性的名称.它创建新片段,而不是返回现有片段.在这个意义上,该方法应该重命名为createItem(int position)Android SDK中的方法.所以这种方法无法帮助我们获取片段.
  2. 基于在后解释支持FragmentPagerAdapterholds参考老片,你应该离开这个片段的创建到FragmentPagerAdapter,并在这样意味着你有没有参考片段或它们的标签.如果你有片段标签,你可以FragmentManager通过调用轻松检索对它的引用findFragmentByTag().我们需要一种方法来在给定的页面位置找出片段的标签.

在类中添加以下辅助方法以检索片段标记并将其发送到findFragmentByTag()方法.

private String getFragmentTag(int viewPagerId, int fragmentPosition)
{
     return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
Run Code Online (Sandbox Code Playgroud)

注意!这是FragmentPagerAdapter在创建新片段时使用的相同方法.请参阅此链接http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104

  • 这不是正确的方法.@Tony Chan的答案是最好和正确的方法. (4认同)

per*_*000 12

我这样做的方法是定义WeakReferences的Hashtable,如下所示:

protected Hashtable<Integer, WeakReference<Fragment>> fragmentReferences;
Run Code Online (Sandbox Code Playgroud)

然后我写了这样的getItem()方法:

@Override
public Fragment getItem(int position) {

    Fragment fragment;
    switch(position) {
    case 0:
        fragment = new MyFirstFragmentClass();
        break;

    default:
        fragment = new MyOtherFragmentClass();
        break;
    }

    fragmentReferences.put(position, new WeakReference<Fragment>(fragment));

    return fragment;
}
Run Code Online (Sandbox Code Playgroud)

然后你可以写一个方法:

public Fragment getFragment(int fragmentId) {
    WeakReference<Fragment> ref = fragmentReferences.get(fragmentId);
    return ref == null ? null : ref.get();
}
Run Code Online (Sandbox Code Playgroud)

这似乎运作良好,我发现它比它更少hacky

"android:switcher:" + viewId + ":" + position
Run Code Online (Sandbox Code Playgroud)

技巧,因为它不依赖于FragmentPagerAdapter的实现方式.当然,如果片段已由FragmentPagerAdapter释放,或者尚未创建,则getFragment将返回null.

如果有人发现这种方法有问题,那么评论非常受欢迎.

  • 我使用的方法非常相似.但是,当从savedState包创建寻呼机时,这会失败.例如:活动进入后台并在调用onSavedStateInstance()后返回到前台.在这种情况下,不会调用getItem()方法. (6认同)
  • 系统重新创建片段后,这将无法正常工作。 (2认同)

mor*_*wai 12

您不需要覆盖instantiateItem也不需要依赖于使用内部makeFragmentName方法创建片段标记的兼容性.instantiateItem是一种公共方法,因此您可以(实际上应该)在onCreate您的活动方法中调用它以获取对片段实例的引用,并在需要时将它们存储在本地变量中.请记住instantiateItemjavadoc中描述的方法startUpdatefinishUpdate方法包围一组调用 :PagerAdapter

对PagerAdapter方法startUpdate(ViewGroup)的调用表明ViewPager的内容即将更改.将跟随对instantiateItem(ViewGroup,int)和/或destroyItem(ViewGroup,int,Object)的一个或多个调用,并且将通过调用finishUpdate(ViewGroup)来发出更新结束信号.

例如,这是在方法中存储对选项卡片段的引用的onCreate方法:

public class MyActivity extends AppCompatActivity {

    Fragment0 tab0; Fragment1 tab1;

    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.myLayout);
        ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
        MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(adapter);
        ((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);

        adapter.startUpdate(viewPager);
        tab0 = (Fragment0) adapter.instantiateItem(viewPager, 0);
        tab1 = (Fragment1) adapter.instantiateItem(viewPager, 1);
        adapter.finishUpdate(viewPager);
    }

    class MyPagerAdapter extends FragmentPagerAdapter {

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

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

        @Override public Fragment getItem(int position) {
            if (position == 0) return new Fragment0();
            if (position == 1) return new Fragment1();
            return null;  // or throw some exception
        }

        @Override public CharSequence getPageTitle(int position) {
            if (position == 0) return getString(R.string.tab0);
            if (position == 1) return getString(R.string.tab1);
            return null;  // or throw some exception
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

instantiateItem将首先尝试从中获取对现有片段实例的引用FragmentManager.只有当它们不存在时,它才会使用getItem适配器中的方法创建新的并将它们"存储"以FragmentManager供将来使用.

一些额外的信息:
如果你没有在你的方法instantiateItem中用startUpdate/ 包围调用那么你就会冒险将你的片段实例永远不会被提交到:当你的活动变为前景时,会自动调用你的片段,但是/ 可能没有(取决于关于实现细节)他们基本上做的是开始/提交a . 这可能导致对创建的片段实例的引用非常快速地丢失(例如,当您旋转屏幕时)并且比必要时更频繁地重新创建.根据您的碎片"重"的程度,它可能会产生不可忽视的性能影响. 但更重要的是,在这种情况下,存储在本地变量上的片段实例将变得陈旧:因为android平台无法从中获取相同的实例,因此可能会创建和使用新的实例,而您的变量仍然会引用旧的变量.finishUpdateonCreateFragmentManagerinstantiateItemstartUpdatefinishUpdate FragmentTransaction

FragmentManager


Pep*_*ijn 10

我创建了这个方法,它可以让我获得对当前片段的引用.

public static Fragment getCurrentFragment(ViewPager pager, FragmentPagerAdapter adapter) {
    try {
        Method m = adapter.getClass().getSuperclass().getDeclaredMethod("makeFragmentName", int.class, long.class);
        Field f = adapter.getClass().getSuperclass().getDeclaredField("mFragmentManager");
        f.setAccessible(true);
        FragmentManager fm = (FragmentManager) f.get(adapter);
        m.setAccessible(true);
        String tag = null;
        tag = (String) m.invoke(null, pager.getId(), (long) pager.getCurrentItem());
        return fm.findFragmentByTag(tag);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    } 
    return null;
}
Run Code Online (Sandbox Code Playgroud)


小智 10

ViewPager2更新的 FragmentStateAdapter

FragmentStateAdaptercreateFragment()代替getItem(). 并且没有这样的方法instantiateView()

因此,当配置更改后重新创建托管Activity/时,不会被调用。这意味着适配器中的片段不会再次创建,而是从. 因此,如果您需要对片段进行一些工作,您可以简单地从 获取它们。FragmentcreateFragment()FragmentManagerFragmentManager

适配器创建:

CustomFragmentAdapter adapter = new CustomFragmentAdapter(getChildFragmentManager(), getLifecycle());
Run Code Online (Sandbox Code Playgroud)

FragmentManager从after Activity/ Fragmentreload中检索片段:

    FragmentManager manager = getChildFragmentManager();
    ArrayList<Fragment> fragments = new ArrayList<>(manager.getFragments());

    for (Fragment fr : fragments) {
        
        // do something
    }
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

72899 次

最近记录:

6 年,7 月 前