【问题标题】:How to properly handle screen rotation with a ViewPager and nested fragments?如何使用 ViewPager 和嵌套片段正确处理屏幕旋转?
【发布时间】:2013-10-15 23:51:32
【问题描述】:

我有这个活动,其中包含一个片段。这个片段布局由一个带有多个片段(实际上是两个)的视图分页器组成。

当视图分页器被创建时,它的适配器被创建,getItem被调用并且我的子片段被创建。太好了。

现在,当我旋转屏幕时,框架会处理片段的重新创建,适配器会在我的主片段的onCreate 中再次创建,但 getItem 永远不会被调用,所以我的适配器包含错误的引用(实际上是空值)而不是两个片段。

我发现片段管理器(即子片段管理器)包含一个名为mActive 的片段数组,当然不能从代码中访问。但是有这个getFragment 方法:

@Override
public Fragment getFragment(Bundle bundle, String key) {
    int index = bundle.getInt(key, -1);
    if (index == -1) {
        return null;
    }
    if (index >= mActive.size()) {
        throwException(new IllegalStateException("Fragement no longer exists for key "
                + key + ": index " + index));
    }
    Fragment f = mActive.get(index);
    if (f == null) {
        throwException(new IllegalStateException("Fragement no longer exists for key "
                + key + ": index " + index));
    }
    return f;
}

I won't comment the typo :)
这是我为了更新对我的片段的引用而在我的适配器构造函数中实现的 hack:

// fm holds a reference to a FragmentManager
Bundle hack = new Bundle();
try {
    for (int i = 0; i < mFragments.length; i++) {
        hack.putInt("hack", i);
        mFragments[i] = fm.getFragment(hack, "hack");
    }
} catch (Exception e) {
    // No need to fail here, likely because it's the first creation and mActive is empty
}

我并不骄傲。这有效,但它很丑。屏幕旋转后获得有效适配器的实际方法是什么?

PS:这里是full code

【问题讨论】:

  • 同样的问题:stackoverflow.com/questions/16299626/…你找到什么了吗?
  • 不,仍在使用我的 hack。我已经检查过您提到的问题,但这并没有解决我的问题。
  • 很抱歉,我还在学习 Android,我无法理解你的 hack。你能为我解释一下吗?您将该代码放在构造函数中,fm 是作为参数给出的 FragmentManager,但是 mFragment 是什么?为什么将所有 int 都设为 0 ?谢谢
  • 你说得对,我忘了在这里修复一个错误(它已在我的代码中修复)。我将编辑我的答案并发布所有代码。
  • 也许这对你有用:stackoverflow.com/questions/7951730/…

标签: android android-fragments android-viewpager android-adapter


【解决方案1】:

我遇到了同样的问题 - 我假设您正在为您的寻呼机适配器子类化 FragmentPagerAdapter(因为 getItem() 特定于 FragmentPagerAdapter)。

我的解决方案是改为子类PagerAdapter 并自己处理片段创建/删除(重新实现一些FragmentPagerAdapter 代码):

public class ListPagerAdapter extends PagerAdapter {
    FragmentManager fragmentManager;
    Fragment[] fragments;

    public ListPagerAdapter(FragmentManager fm){
        fragmentManager = fm;
        fragments = new Fragment[5];
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        assert(0 <= position && position < fragments.length);
        FragmentTransaction trans = fragmentManager.beginTransaction();
        trans.remove(fragments[position]);
        trans.commit();
        fragments[position] = null;
}

    @Override
    public Fragment instantiateItem(ViewGroup container, int position){
        Fragment fragment = getItem(position);
        FragmentTransaction trans = fragmentManager.beginTransaction();
        trans.add(container.getId(),fragment,"fragment:"+position);
        trans.commit();
        return fragment;
    }

    @Override
    public int getCount() {
        return fragments.length;
    }

    @Override
    public boolean isViewFromObject(View view, Object fragment) {
        return ((Fragment) fragment).getView() == view;
    }

    public Fragment getItem(int position){
        assert(0 <= position && position < fragments.length);
        if(fragments[position] == null){
            fragments[position] = ; //make your fragment here
        }
        return fragments[position];
    }
}

希望这会有所帮助。

【讨论】:

  • 绝妙的解决方案!谢谢!
  • 是的,我花了 3 年时间才接受您的解决方案。谢谢! :D
  • 这是一个解决方案,但默认行为是出乎意料的,或者我可能遗漏了什么?知道背后的原因是什么吗?
  • 不错的答案,但对于更简单的解决方案,请查看stackoverflow.com/a/29288093/1299234
【解决方案2】:

我的回答有点类似于 Joshua Hunt 的回答,但是通过在 finishUpdate 方法中提交事务,您可以获得更好的性能。每次更新一次交易而不是两次。 代码如下:

private class SuchPagerAdapter extends PagerAdapter{

    private final FragmentManager mFragmentManager;
    private SparseArray<Fragment> mFragments;
    private FragmentTransaction mCurTransaction;

    private SuchPagerAdapter(FragmentManager fragmentManager) {
        mFragmentManager = fragmentManager;
        mFragments = new SparseArray<>();
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = getItem(position);
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        mCurTransaction.add(container.getId(),fragment,"fragment:"+position);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        mCurTransaction.detach(mFragments.get(position));
        mFragments.remove(position);
    }

    @Override
    public boolean isViewFromObject(View view, Object fragment) {
        return ((Fragment) fragment).getView() == view;
    }

    public Fragment getItem(int position) {         
        return YoursVeryFragment.instantiate();
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();
        }
    }


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

}

【讨论】:

  • 来自mitenko 的评论: mFragments 没有被填充到 instantiateItem 中,需要mFragments.put(position, fragment); 在里面,否则你最终会遇到这个错误:[尝试从视图中删除片段会在 mNextAnim 上给我 NullPointerException。](stackoverflow.com/questions/22489703/…)。
【解决方案3】:

为什么上面的解决方案如此复杂?看起来有点矫枉过正。 我只是通过替换从 FragmentPagerAdapter 扩展的类中的新引用来解决它

 @Override
public Object instantiateItem(ViewGroup container, int position) {
    frags[position] = (Fragment) super.instantiateItem(container, position);
    return frags[position];
}

所有适配器的代码都是这样的

public class RelationsFragmentsAdapter extends FragmentPagerAdapter {

private final String titles[] = new String[3];
private final Fragment frags[] = new Fragment[titles.length];

public RelationsFragmentsAdapter(FragmentManager fm) {
    super(fm);
    frags[0] = new FriendsFragment();
    frags[1] = new FriendsRequestFragment();
    frags[2] = new FriendsDeclinedFragment();

    Resources resources = AppController.getAppContext().getResources();

    titles[0] = resources.getString(R.string.my_friends);
    titles[1] = resources.getString(R.string.my_new_friends);
    titles[2] = resources.getString(R.string.followers);
}

@Override
public CharSequence getPageTitle(int position) {
    return titles[position];
}

@Override
public Fragment getItem(int position) {
    return frags[position];
}

@Override
public int getCount() {
    return frags.length;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    frags[position] = (Fragment) super.instantiateItem(container, position);
    return frags[position];
}

}

【讨论】:

  • 谢谢,这是一个很好的答案。轻量级、快速、易于实现、无静态(泄漏)引用。而且很容易理解它为什么起作用。
  • 但这会在你每次旋转屏幕时创建新的片段实例
【解决方案4】:

我的代码:

    public class SampleAdapter extends FragmentStatePagerAdapter {

    private Fragment mFragmentAtPos2;
    private FragmentManager mFragmentManager;
    private Fragment[] mFragments = new Fragment[3];


    public SampleAdapter(FragmentManager mgr) {
        super(mgr);
        mFragmentManager = mgr;
        Bundle hack = new Bundle();
        try {
            for (int i = 0; i < mFragments.length; i++) {
                hack.putInt("hack", i);
                mFragments[i] = mFragmentManager.getFragment(hack, "hack");
            }
        } catch (Exception e) {
            // No need to fail here, likely because it's the first creation and mActive is empty
        }

    }

    public void switchFrag(Fragment frag) {

        if (frag == null) {
            Dbg.e(TAG, "- switch(frag) frag is NULL");
            return;
        } else Dbg.v(TAG, "- switch(frag) - frag is " + frag.getClass());
        // We have to check for mFragmentAtPos2 null in case of first time (only Mytrips fragment being instatiante).
        if (mFragmentAtPos2!= null) 
            mFragmentManager.beginTransaction()  
                .remove(mFragmentAtPos2)
                .commit();
        mFragmentAtPos2 = frag;
        notifyDataSetChanged();
    }

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

    @Override
    public int getItemPosition(Object object) {
        Dbg.v(TAG,"getItemPosition : "+object.getClass());
        if (object instanceof MyTripsFragment
                || object instanceof FindingDriverFragment
                || object instanceof BookingAcceptedFragment
                || object instanceof RideStartedFragment
                || object instanceof RideEndedFragment
                || object instanceof ContactUsFragment
                )
            return POSITION_NONE;
        else return POSITION_UNCHANGED;

    }

    @Override
    public Fragment getItem(int position) {
        Dbg.v("SampleAdapter", "getItem called on: "+position);
        switch (position) {
        case 0: 
            if (snapbookFrag==null) {
                snapbookFrag = new SnapBookingFragment();
                Dbg.e(TAG, "snapbookFrag created");
            }
            return snapbookFrag;
        case 1: 
            if(bookingFormFrag==null) {
                bookingFormFrag = new BookingFormFragment();
                Dbg.e(TAG, "bookingFormFrag created");
            }
            return bookingFormFrag;
        case 2: 
            if (mFragmentAtPos2 == null) {
                myTripsFrag = new MyTripsFragment();
                mFragmentAtPos2 = myTripsFrag;
                return mFragmentAtPos2;
            }
            return mFragmentAtPos2;
        default:
            return(new SnapBookingFragment());
        }
    }
}

【讨论】:

    【解决方案5】:

    参考simekadam's 解决方案,mFragments 没有被填充到 instantiateItem 中并且需要 mFragments.put(position, fragment); 在里面,否则你会得到这个错误:Trying to remove fragment from view gives me NullPointerException on mNextAnim

    【讨论】:

    • 我已将您的评论添加到答案中。您还可以建议对发布的修改提出建议,只要它确实有意义且不会改变意思。
    【解决方案6】:

    问题是FragmentPageAdapter 中的getItem() 名称错误。它应该被命名为createItem()。 因为getItem() 的工作方式是创建片段,调用它来查询/查找片段是不安全的。

    我的建议是复制当前的 FragmentPagerAdapter 并以这种方式进行更改:

    添加:

        public abstract Fragment createFragment(int position);
    

    并将 getItem 更改为:

    public Fragment getItem(int position) {
        if(containerId!=null) {
            final long itemId = getItemId(position);
            String name = makeFragmentName(containerId, itemId);
            return mFragmentManager.findFragmentByTag(name);
        } else {
            return null;
        }
    }
    

    最后将其添加到实例化项目中:

        if(containerId==null)
            containerId = container.getId();
        else if(containerId!=container.getId())
            throw new RuntimeException("Container id not expected to change");
    

    完整代码this gist

    我认为这种实现更安全、更易于使用,并且与 Google 工程师的原始适配器具有相同的性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-04
      • 1970-01-01
      • 1970-01-01
      • 2013-03-11
      • 1970-01-01
      相关资源
      最近更新 更多