FragmentPagerAdapter在屏幕旋转时不会重新创建Fragment吗?

9

我有一个ViewPager(扩展了FragmentPagerAdapter),其中包含两个Fragments。我需要的是当我在它们之间滑动时,只需刷新每个Fragment中的一个ListView。为此,我实现了ViewPager.OnPageChangeListener接口(即onPageScrollStateChanged)。为了保存对Fragments的引用,我使用了一个HashTable。我在getItem()方法中将Fragments的引用存储在HashTable中:

  @Override
        public Fragment getItem(int num) {
            if (num == 0) {
                Fragment itemsListFragment = new ItemsListFragment();
                mPageReferenceMap.put(num, itemsListFragment);
                return itemsListFragment;
            } else {
                Fragment favsListFragment = new ItemsFavsListFragment();
                mPageReferenceMap.put(num, favsListFragment);
                return favsListFragment;
            }
        }

当我从一个Fragment滑动到另一个Fragment时,会触发onPageScrollStateChanged事件,我使用HashTable在两个Fragments中调用所需的方法(刷新):

 public void refreshList() {
        ((ItemsListFragment) mPageReferenceMap.get(0)).refresh();
        ((ItemsFavsListFragment) mPageReferenceMap.get(1)).refresh();
    }

一切都很顺利,直到出现方向改变事件。之后,在refresh()方法中的代码如下:

public void refresh() {
        mAdapter.changeCursor(mDbHelper.getAll());
        getListView().setItemChecked(-1, true); // The last row from a exception trace finishes here (my class).
    }

IllegalStateException的结果:

java.lang.IllegalStateException: Content view not yet created
        at android.support.v4.app.ListFragment.ensureList(ListFragment.java:328)
        at android.support.v4.app.ListFragment.getListView(ListFragment.java:222)
        at ebeletskiy.gmail.com.passwords.ui.ItemsFavsListFragment.refresh(ItemsFavsListFragment.java:17)

假设确实没有创建“Content”视图,我在“onActivityCreated()”方法中设置了布尔变量为“true”,并使用“if/else”条件来调用“getListView()”或不调用,“这表明活动和内容视图成功创建。
然后我进行调试,看看“FragmentPagerAdapter”何时调用“getItem()”,发现该方法在“方向更改”事件后未被调用。所以看起来像是“ViewPager”保留了旧的“Fragments”的引用。这只是我的假设。
那么,有没有办法强制“ViewPager”重新调用“getItem()”,以便我可以使用当前“Fragments”的正确引用?也许还有其他解决方案?非常感谢。
3个回答

5
然后我在调试时查看FragmentPagerAdapter何时调用getItem(),结果发现此方法在方向更改事件后未被调用。因此看起来ViewPager保留了对旧Fragment的引用。
片段应该会自动重新创建,就像在配置更改时重新创建任何片段一样。特殊情况是如果您使用了setRetainInstance(true),在这种情况下它们应该是之前相同的片段对象。
所以,有什么办法可以强制ViewPager再次调用getItem(),以便我可以使用当前Fragment的正确引用?
那里的片段有什么问题吗?

我在那里没有使用 setRetainInstance()。问题出在方向改变后弹出的异常上。我只是假设旧的片段正在使用中,因为我检查了onActivityCreated()的条件,它们都被执行了。 - Eugene
@siik:你什么时候调用这个 refresh() 方法?以及你什么时候将新创建的片段提取出来以填充你那个(呕)Hashtable - CommonsWare
我在onPageScrollStateChanged()方法中调用了refresh()方法(该方法是ViewPager.OnPageChangeListener接口的实现,即当我在Fragments之间滑动时)。并且我在扩展FragmentPagerAdapter类的getItem()方法中填充了HashTable - Eugene
我刚刚在两个片段中添加了 setRetainInstance(true),现在不再出现这样的异常了,这证实了我的假设:在 方向更改 后,Hashtable 中的引用已经过时。但仍然很好奇如何在没有使用 setRetainInstance(true) 的情况下处理这种情况。 - Eugene
3
@siik说:“这正确认证了我的假设,即Hashtable中的引用在方向更改后已过期”-- 完全正确。我以为你已经知道了,抱歉。“仍然很好奇如何在没有setRetainInstance(true)的情况下处理这种情况”-- 给ListView小部件分配唯一标签,然后在ViewPager上使用findViewWithTag()查找它们,而不是维护自己的单独集合。 - CommonsWare
请参考此问题 - https://dev59.com/gnbZa4cB1Zd3GeqPBhOd - Nick

0

您可以清除已保存的实例状态

    protected void onCreate(Bundle savedInstanceState) {
        clearBundle(savedInstanceState);
        super.onCreate(savedInstanceState, R.layout.activity_car);
    }


    private void clearBundle(Bundle savedInstanceState) {
        if (savedInstanceState != null) {
            savedInstanceState.remove("android:fragments");
            savedInstanceState.remove("android:support:fragments");
            savedInstanceState.remove("androidx.lifecycle.BundlableSavedStateRegistry.key");
            savedInstanceState.remove("android:lastAutofillId");
        }
    }

0

我花了几天时间寻找解决这个问题的方法,发现了许多要点:

  • 使用FragmentPagerAdapter代替FragmentStatePagerAdapter

  • 使用FragmentStatePagerAdapter代替FragmentPagerAdapter

  • 在FragmentPagerAdapter的getItemPosition覆盖中返回POSITION_NONE

  • 如果需要动态更改片段,请勿使用FragmentPagerAdapter

  • 还有许多其他的...

在我的应用程序中,就像Eugene一样,我自己管理已创建片段的实例。我将它们保存在一个HashMap<String,Fragment>中的某个专门的类中,因此片段永远不会被释放,加快了我的应用程序速度(但消耗了更多资源)。

问题是当我旋转我的平板电脑(和手机)时。 getItem(int)不再为该片段调用,我无法更改它。

我花了很多时间才真正找到了解决方案,所以我需要与StackOverflow社区分享,感谢他们帮助了我很多很多次...

虽然要费一番功夫才能找到这个问题的解决方案,但它其实很简单:

只需在FragmentPagerAdapter扩展的构造函数中保留FragmentManager的引用即可:

public class Manager_Pager extends FragmentPagerAdapter {

    private final FragmentManager mFragmentManager;
    private final FragmentActivity mContext;

    public Manager_Pager(FragmentActivity context) {

        super( context.getSupportFragmentManager() );

        this.mContext = context;
        this.mFragmentManager = context.getSupportFragmentManager();
    }

    @Override
    public int getItemPosition( Object object ) {

// here, check if this fragment is an instance of the
//   ***FragmentClass_of_you_want_be_dynamic***

        if (object instanceof FragmentClass_of_you_want_be_dynamic) {

// if true, remove from ***FragmentManager*** and return ***POSITION_NONE***
//   to force a call to ***getItem***

            mFragmentManager.beginTransaction().remove((Fragment) object).commit();
            return POSITION_NONE;
        }

        //don't return POSITION_NONE, avoid fragment recreation.
        return super.getItemPosition(object);
    }

    @Override
    public Fragment getItem( int position ) {


        if ( position == MY_DYNAMIC_FRAGMENT_INDEX){

            Bundle args = new Bundle();
            args.putString( "anything", position );
            args.putString( "created_at", ALITEC.Utils.timeToStr() );

            return Fragment.instantiate( mContext, FragmentClass_of_you_want_be_dynamic.class.getName(), args );

        }else

        if ( position == OTHER ){

            //...

        }else

        return Fragment.instantiate( mContext, FragmentDefault.class.getName(), null );

    }
}

就这样。它会像魔法一样运行...


1
屏幕方向改变时,getItem方法从未被调用,对我没有起作用。 - JosephM
this.mFragmentManager = context.getSupportFragmentManager(); 这里的“this.mFragmentManager = context.getSupportFragmentManager();”是指在上下文中获取Support Fragment Manager。 - alijunior

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接