在fragmentpageradapter中重用碎片


70

我有一个viewpager,可以通过片段进行分页。我的FragmentPagerAdapter子类在该getItem方法中创建了一个看起来很浪费的新片段。是否有一个FragmentPagerAdapter相当于convertViewlistAdapter,使我重新使用已创建的片段?我的代码如下。

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

Answers:


102

FragmentPagerAdapter已缓存Fragments为您服务。每个片段都分配了一个标签,然后FragmentPagerAdapter尝试调用findFragmentByTag。它只调用getItem如果从结果findFragmentByTagnull。因此,您不必自己缓存片段。


4
蝙蝠我认为这不是解决方案。我有类似的问题。我在ViewPager中有10个屏幕,所有屏幕都只包含带有不同参数的相同片段。我不需要10个实例。3足够了。当用户向右滚动时,大多数左片段都可以重用。
ATom 2012年

@ATom-滚动时,如果导航到任何片段,则超出+/- 1索引的片段都会被销毁并重新创建。尝试将日志输出放入onCreateView方法中,以查看每个片段何时实例化
Matt Taylor

2
@MattTaylor如果您使用的是ViewPager,则可以使用setOffscreenPageLimit保留相同的实例
An-droid

@MattTaylor-这有点不正确。片段本身不应被销毁(除非您的应用程序的可用内存不足),但这与被销毁的片段视图不同(一旦有问题的片段超出OffscreenPageLimint,就会发生这种情况)。在Fragment的onCreate中添加第二条日志语句,您将看到不应在每次调用onCreateView时调用它。
杰夫2014年

1
仍然没有解决办法,对吗?每个解决方案毕竟仍在内存中保留10个片段。相反,只有2或3的
马蒂亚斯

67

杰夫的帖子的附录:

您可以FragmentFragmentPagerAdapter使用中获得参考findFragmentByTag()。标签名称是通过以下方式生成的:

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


19
对于像我这样的人请注意:viewId是ViewPager的ID
nspo 2013年

@Kopfgeldjaeger我看到了您的代码getFragmentByPosition。片段的位置在哪里?
gkiko 2014年

借用@GregEnnis与State pager不兼容,有什么想法吗?
2015年

1
@trippedout @GregEnnis在下面尝试我的答案。您可以保存对Fragments创建者的引用,FragmentStatePagerAdapter并使用这些引用,而不用尝试使用tagsFragmentStatePagerAdapter不使用)查找它们。
东尼·陈

首先,此答案中的代码取决于与内部命名的兼容性:如果此机制发生更改,此代码将停止工作。其次有实在没有理由通过标签来获得裁判给片段,你可以简单地存储它们,当你调用instantiateItemonCreate这里描述:stackoverflow.com/questions/14035090/...
morgwai

23

似乎很多查看此问题的人都在寻找引用/Fragments创建的方法。我想提供此解决方案,而不依赖于此其他答案使用的内部创建。FragmentPagerAdapterFragmentStatePagerAdaptertags

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


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

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

无需内部解决方案的替代解决方案 tag

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

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


不要忘记,根据您的设计ActivityFragments您正在尝试进行的工作可能存在或可能不存在,因此您必须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,这救了我。
迪克·卢卡斯

1
RE:当前解决方案的问题...值得注意的是,如果您使用支持库中的片段,那么您实际上并不是在依靠设备的“内部代码”来执行此操作。您将依赖于应用程序随附的支持库代码(如果您偏执狂或需要进行更改,甚至可以将源代码检查到存储库中)。
Geoff

1
@geoff很好,但这甚至是有文档记录的功能吗?更不用说它看起来很糟糕-您有一个不错的,有意义的,基于函数的标记,您可以定义自己...然后就拥有了。
kaay 2016年

在描述stackoverflow.com/questions/14035090/...真的没有理由覆盖instantiateItem存储引用,你都应该调用 instantiateItem你的onCreate,所以你可以存储的方式有一个参考。
morgwai

17

如果片段仍在内存中,则可以使用此功能找到它。

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

v4支持api的示例代码。


首先,此答案中的代码取决于与内部命名的兼容性:如果此机制发生更改,此代码将停止工作。其次,实际上没有理由按标签获取对Fragments的引用,因为您可以按如下所述在onCreate中调用
InstantiateItem


0

我知道,从理论上讲,这不是对问题的答案,而是另一种方法。

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

在尝试了许多不同的东西之后,我终于使用 BroadCastReceiver。当您需要对可见片段进行处理并将其捕获到片段中时,只需发送广播即可。

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

干杯

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.