首页 > java > 如何在屏幕旋转期间处理FragmentTransaction(缓存)隐藏的片段?

如何在屏幕旋转期间处理FragmentTransaction(缓存)隐藏的片段? (How to handle FragmentTransaction (cached) hidden fragments during screen rotation?)

问题

在我的主要布局中,我有一个包含片段的中央容器。

我开始使用FragmentTransaction.replace(...)将片段替换为其他片段,但这会在每次替换时重新创建每个片段,这会使UI滞后(某些片段具有复杂的布局等)。

有些建议使用.show()和.hide()来“缓存”渲染的片段,而不是replace(),这导致下面的代码:

private HashMap<String, Fragment> mCachedFragments = new HashMap<String, Fragment>();
private Fragment mCurrentFragment = null;
@Override
public void onNavigationDrawerItemSelected(int position) {
    if (position == mCurrentSectionID)
        return;

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);

    if (mCurrentFragment != null) {
        Log.d("TRANSACTION", "putting fragment "+mCurrentFragment.getClass().getName());
        mCachedFragments.put(mCurrentFragment.getClass().getName(), mCurrentFragment);
        transaction.hide(mCurrentFragment);
    }
    Log.d("Nav", "selected: " + position);

    Fragment dest = null;
    switch (position) {
        case BitsListFragment.FRAGMENT_ID:
            dest = mCachedFragments.get(BitsListFragment.class.getName());
            if (dest == null) {
                dest = BitsListFragment.newInstance();
                transaction
                        .add(R.id.container, dest);
            } else {
                Log.d("TRANSACTION", "bitlist retrieved");
                transaction.show(dest);
            }
            onSectionAttached(BitsListFragment.FRAGMENT_ID);
            break;
        case AchievementsFragment.FRAGMENT_ID:
            dest = mCachedFragments.get(AchievementsFragment.class.getName());
            if (dest == null) {
                dest = AchievementsFragment.newInstance();
                transaction
                        .add(R.id.container, dest);
            } else {
                Log.d("TRANSACTION", "achievements retrieved");
                transaction.show(dest);
            }
            onSectionAttached(AchievementsFragment.FRAGMENT_ID);
            break;
        case StatsFragment.FRAGMENT_ID:
            dest = mCachedFragments.get(StatsFragment.class.getName());
            if (dest == null) {
                dest = StatsFragment.newInstance();
                transaction
                        .add(R.id.container, dest);
            } else {
                Log.d("TRANSACTION", "stats retrieved");
                transaction.show(dest);
            }
            onSectionAttached(StatsFragment.FRAGMENT_ID);
            break;
    }

    mCurrentFragment = dest;
    transaction.commit();
}

只要不重新创建Activity,这里的代码就可以完美运行。检索并显示片段,并在转换发生时隐藏currentFragment。

如您所见,我基本上保留了对CurrentFragment的引用以及所有缓存片段的哈希映射。问题是当我旋转屏幕时,会重新创建MainActivity,并将这些引用重置为null或清空。最终导致BitsListFragment多次创建并在屏幕上重叠显示。

有没有更好的方法来缓存片段/替换片段而无需重新创建它们?

谢谢!

解决方法

实际上找到了另一种方法:我可以将片段添加到backstack,然后使用相应的标签检索它,而不是自己保留引用。这个让我们的fragmentManager自己管理缓存。第二次访问片段时,不会重新创建它。

你应该注意的唯一黑客是覆盖onBackPressed(),这样如果你不需要,你就不会返回到最后一个片段。

@Override
public void onNavigationDrawerItemSelected(int position) {
    if (position == mCurrentSectionID)
        return;

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);

    Fragment dest;
    switch (position) {
        case BitsListFragment.FRAGMENT_ID:
            dest = fragmentManager.findFragmentByTag(BitsListFragment.class.getName());
            if (dest == null) {
                Log.d("TRANSACTION", "instanciating new fragment");
                dest = BitsListFragment.newInstance();
            }
            transaction.replace(R.id.container, dest, BitsListFragment.class.getName());
            transaction.addToBackStack(BitsListFragment.class.getName());
            break;
        case AchievementsFragment.FRAGMENT_ID:
            dest = fragmentManager.findFragmentByTag(AchievementsFragment.class.getName());
            if (dest == null) {
                Log.d("TRANSACTION", "instanciating new fragment");
                dest = AchievementsFragment.newInstance();
            }
            transaction.replace(R.id.container, dest, AchievementsFragment.class.getName());
            transaction.addToBackStack(AchievementsFragment.class.getName());
            break;
        case StatsFragment.FRAGMENT_ID:
            dest = fragmentManager.findFragmentByTag(StatsFragment.class.getName());
            if (dest == null) {
                Log.d("TRANSACTION", "instanciating new fragment");
                dest = StatsFragment.newInstance();
            }
            transaction.replace(R.id.container, dest, StatsFragment.class.getName());
            transaction.addToBackStack(StatsFragment.class.getName());
            break;
    }

    transaction.commit();
    onSectionAttached(position);
}

问题

In my main layout I have a central container which contains a fragment.

I started with FragmentTransaction.replace(...) to replace the fragment with other fragments but this recreate each fragment on each replacement, which makes the UI laggy (some fragments have complexe layout etc).

Instead of replace(), some suggest using .show() and .hide() to "cache" rendered fragments, and which leads to the code below:

private HashMap<String, Fragment> mCachedFragments = new HashMap<String, Fragment>();
private Fragment mCurrentFragment = null;
@Override
public void onNavigationDrawerItemSelected(int position) {
    if (position == mCurrentSectionID)
        return;

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);

    if (mCurrentFragment != null) {
        Log.d("TRANSACTION", "putting fragment "+mCurrentFragment.getClass().getName());
        mCachedFragments.put(mCurrentFragment.getClass().getName(), mCurrentFragment);
        transaction.hide(mCurrentFragment);
    }
    Log.d("Nav", "selected: " + position);

    Fragment dest = null;
    switch (position) {
        case BitsListFragment.FRAGMENT_ID:
            dest = mCachedFragments.get(BitsListFragment.class.getName());
            if (dest == null) {
                dest = BitsListFragment.newInstance();
                transaction
                        .add(R.id.container, dest);
            } else {
                Log.d("TRANSACTION", "bitlist retrieved");
                transaction.show(dest);
            }
            onSectionAttached(BitsListFragment.FRAGMENT_ID);
            break;
        case AchievementsFragment.FRAGMENT_ID:
            dest = mCachedFragments.get(AchievementsFragment.class.getName());
            if (dest == null) {
                dest = AchievementsFragment.newInstance();
                transaction
                        .add(R.id.container, dest);
            } else {
                Log.d("TRANSACTION", "achievements retrieved");
                transaction.show(dest);
            }
            onSectionAttached(AchievementsFragment.FRAGMENT_ID);
            break;
        case StatsFragment.FRAGMENT_ID:
            dest = mCachedFragments.get(StatsFragment.class.getName());
            if (dest == null) {
                dest = StatsFragment.newInstance();
                transaction
                        .add(R.id.container, dest);
            } else {
                Log.d("TRANSACTION", "stats retrieved");
                transaction.show(dest);
            }
            onSectionAttached(StatsFragment.FRAGMENT_ID);
            break;
    }

    mCurrentFragment = dest;
    transaction.commit();
}

The code here works perfectly as long as the Activity does not gets recreated. The fragments gets retrieved and shown, and currentFragment is hidden when transition takes place.

As you can see, I'm basically keeping a reference to the CurrentFragment and a hashmap of all the cached fragments. The problem is that when I rotation the screen, the MainActivity gets recreated and these references are reset to null or emptied. Which ends up with BitsListFragment getting created multiple times and shown on screen overlapped.

Is there a better way to cache fragments/replace fragments without recreating them?

Thanks!

解决方法

Actually found another way to do this: instead of keeping an reference on my own, we can add the fragment to the backstack and then retrieve it using corresponding tag. This let's fragmentManager manages the caching by itself. And the second time you access a fragment, it doesn't gets recreated.

The only hack that you should watch out is to override onBackPressed() so that you don't return to last fragment if you don't need to.

@Override
public void onNavigationDrawerItemSelected(int position) {
    if (position == mCurrentSectionID)
        return;

    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);

    Fragment dest;
    switch (position) {
        case BitsListFragment.FRAGMENT_ID:
            dest = fragmentManager.findFragmentByTag(BitsListFragment.class.getName());
            if (dest == null) {
                Log.d("TRANSACTION", "instanciating new fragment");
                dest = BitsListFragment.newInstance();
            }
            transaction.replace(R.id.container, dest, BitsListFragment.class.getName());
            transaction.addToBackStack(BitsListFragment.class.getName());
            break;
        case AchievementsFragment.FRAGMENT_ID:
            dest = fragmentManager.findFragmentByTag(AchievementsFragment.class.getName());
            if (dest == null) {
                Log.d("TRANSACTION", "instanciating new fragment");
                dest = AchievementsFragment.newInstance();
            }
            transaction.replace(R.id.container, dest, AchievementsFragment.class.getName());
            transaction.addToBackStack(AchievementsFragment.class.getName());
            break;
        case StatsFragment.FRAGMENT_ID:
            dest = fragmentManager.findFragmentByTag(StatsFragment.class.getName());
            if (dest == null) {
                Log.d("TRANSACTION", "instanciating new fragment");
                dest = StatsFragment.newInstance();
            }
            transaction.replace(R.id.container, dest, StatsFragment.class.getName());
            transaction.addToBackStack(StatsFragment.class.getName());
            break;
    }

    transaction.commit();
    onSectionAttached(position);
}
相似信息