【问题标题】:reusing fragments in a fragmentpageradapter在片段页面适配器中重用片段
【发布时间】:2011-10-22 00:09:06
【问题描述】:

我有一个分页浏览器。我的FragmentPagerAdapter 子类在getItem 方法中创建了一个新片段,这似乎很浪费。是否有与listAdapter 中的convertView 等效的FragmentPagerAdapter,这将使我能够重用已经创建的片段?我的代码如下。

public class ProfilePagerAdapter extends FragmentPagerAdapter {

    ArrayList<Profile> mProfiles = new ArrayList<Profile>();

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

    /**
     * Adding a new profile object created a new page in the pager this adapter is set to.
     * @param profile
     */
    public void addProfile(Profile profile){
        mProfiles.add(profile);
    }

    @Override
    public int getCount() {
        return mProfiles.size();
    }

    @Override
    public Fragment getItem(int position) {
        return new ProfileFragment(mProfiles.get(position));
    }
}

【问题讨论】:

    标签: android android-fragments fragment android-compatibility


    【解决方案1】:

    FragmentPagerAdapter 已经为您缓存了Fragments。每个片段都分配了一个标签,然后FragmentPagerAdapter 尝试调用findFragmentByTag。如果findFragmentByTag 的结果是null,它只会调用getItem。所以你不应该自己缓存片段。

    【讨论】:

    • 蝙蝠我认为这不是解决方案。我有类似的问题。我在 ViewPager 中有 10 个屏幕,它们都包含相同的片段,但参数不同。我不需要 10 个实例。 3就够了。当用户向右滚动时,可以重复使用最左边的片段。
    • @ATom - 当您滚动时,超过 +/- 1 个索引的片段会在您导航到它们时被销毁并重新创建。尝试将日志输出放入onCreateViewmethod 以查看每个片段何时实例化
    • @MattTaylor 如果您使用的是 ViewPager,您可以使用 setOffscreenPageLimit 来保持相同的实例
    • @MattTaylor - 这有点不正确。片段本身不应被销毁(除非您的应用程序可用内存不足),但这与被销毁的片段视图不同(一旦相关片段超出 OffscreenPageLimint 就会发生)。将第二条日志语句添加到 Fragment 的 onCreate 中,您会发现每次调用 onCreateView 时都不应该调用它。
    • 还是没有解决办法吧?毕竟,每个解决方案仍然在内存中保存 10 个片段。而不是只有 2 或 3 个。
    【解决方案2】:

    Geoff 帖子的附录:

    您可以使用findFragmentByTag()FragmentPagerAdapter 中获取对您的Fragment 的引用。标签的名称是这样生成的:

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

    其中 viewId 是 ViewPager 的 id

    看这个链接:http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104

    【讨论】:

    • 像我这样的人注意:viewId 是 ViewPager 的 id
    • @Kopfgeldjaeger 我看到你编码getFragmentByPosition。 fragment的位置是什么?
    • Seconding @GregEnnis 不适用于 State 寻呼机,有什么想法吗?
    • @trippedout @GregEnnis 在下面尝试my answer。您可以保存对FragmentStatePagerAdapter 创建的Fragments 的引用并使用它们,而不是尝试使用tagsFragmentStatePagerAdapter 不使用)来查找它们。
    • 首先,此答案中的代码取决于与内部命名的兼容性:如果此机制发生更改,此代码将停止工作。其次,实际上没有理由通过标签获取对 Fragments 的引用,因为您可以在您的 instantiateItem 中调用 instantiateItem 时简单地存储它们,如下所述:stackoverflow.com/questions/14035090/…
    【解决方案3】:

    似乎很多查看此问题的人都在寻找一种方法来引用由FragmentPagerAdapter/FragmentStatePagerAdapter 创建的Fragments。我想提供我的解决方案,而不依赖 internally 创建的 tags,此处其他答案使用。

    作为奖励,此方法也适用于FragmentStatePagerAdapter。有关详细信息,请参阅下面的注释。


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

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

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

    这是一个简单示例,说明如何获取对 FragmentPagerAdapter 返回的 Fragments 的引用,该引用不依赖于 Fragments 上设置的内部 tags。关键是覆盖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();
            }
        }
    }
    

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

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

    请注意,此方法不依赖于模仿FragmentPagerAdapter 设置的内部tag,而是使用适当的API 来检索它们。这样,即使 tagSupportLibrary 的未来版本中发生更改,您仍然是安全的。


    不要忘记,根据您的 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...
    }
    

    【讨论】:

    • 他用m1stFragment = (FragmentA) createdFragment; 建议的第一个策略对我有用,我认为这是最好的解决方案。出于某种原因,备用策略String firstTag = createdFragment.getTag(); 对我不起作用。非常感谢@Turbo,这救了我。
    • RE:当前解决方案的问题...值得注意的是,如果您使用片段的支持库,那么您实际上并没有依赖设备的“内部代码”来执行此操作.您将依赖应用随附的支持库代码(如果您偏执或需要进行更改,您甚至可以将源代码检查到您的存储库中)。
    • @geoff 好点,但这甚至是一个记录的功能吗?更不用说它看起来很糟糕——你有很好的、有意义的、基于函数的标签,你自己定义了……然后你就有了。
    • stackoverflow.com/questions/14035090/… 中所述,确实没有理由重写instantiateItem 来存储引用,因为您应该在onCreate调用 instantiateItem,这样您就可以顺便存储一个参考。
    【解决方案4】:

    如果片段还在内存中,你可以通过这个函数找到它。

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

    v4 支持 api 的示例代码。

    【讨论】:

    • 首先,此答案中的代码取决于与内部命名的兼容性:如果此机制发生更改,此代码将停止工作。其次,实际上没有理由通过标签获取对 Fragments 的引用,因为您可以在 onCreate 中调用 instantiateItem 时简单地存储它们,如下所述:stackoverflow.com/questions/14035090/…
    【解决方案5】:

    为未来的读者!

    如果您正在考虑通过 viewpager 重用片段,最好的解决方案是使用 ViewPager 2,因为 View Pager 2 使用了 RecyclerView。

    【讨论】:

      【解决方案6】:

      我知道这(理论上)不是问题的答案,而是一种不同的方法。

      我遇到了需要刷新可见片段的问题。无论我尝试过什么,都失败了,惨遭失败……

      在尝试了这么多不同的事情之后,我终于用BroadCastReceiver 完成了这个。当您需要对可见片段做某事并在片段中捕获它时,只需发送广播即可。

      如果您还需要某种响应,也可以通过广播发送。

      干杯

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多