Android碎片从以前的状态存在,不能删除



我有以下设置,这是很常见的:在风景模式下,我有2个片段- A和B.在肖像模式下,我只有片段A.我试图检测我是否在第二个设置模式,只是一个简单的检查:

getSupportFragmentManager().findFragmentById(R.id.frag_b) == null

这是正常工作,直到我在2片段模式和旋转设备到1片段模式-之后,经理发现片段B和而不是返回null。我相信片段管理器以某种方式保存和加载其状态从以前的设置。第一个问题是,为什么会这样,我能做些什么?

第二个问题-我试图删除片段,但不能这样做。下面是我的尝试:

Fragment f = manager.findFragmentById(R.id.frag_b);
manager.beginTransaction().remove(f).commit();
f = manager.findFragmentById(R.id.frag_b); // still there

我猜remove()没有工作,因为它没有使用add()添加,而是从以前的状态xml加载。在这种情况下,有没有办法从管理器中删除一个片段?

注:对我来说,解决办法是用另一种方法来检测我处于哪种模式。我已经有这个了,只是需要知道它是如何工作的,以便更好地理解片段和它们的行为。

你可以检测你是纵向还是横向使用这个:

getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;

作为碎片的新手,经过一天的挣扎,我想我理解了碎片背后的大部分逻辑和它们的用法。片段管理器显示的片段不是为当前方向定义的片段,这不是一个bug,而是一个特性。以下是一些观察总结:

  1. 当更改配置时,FragmentManager保存它当前拥有的所有片段并将它们加载回来,以便它们在容器活动的onCreate()方法中准备就绪。这意味着如果你在一些布局中有片段A和B,并且你将设备旋转到只有A应该的状态-你仍然会在FragmentManager中找到B。它可能没有被添加(检查Fragment.isAdded()),或者可能被添加到其他容器,现在不可见,但它在那里。这是非常有用的,因为B保存了它的状态(假设你在B的生命周期函数中做得很好),你不必在活动级别上照顾它。也许,在将来的某个时候,你想动态地将片段B添加到你的UI中,它将从以前的配置中保存所有状态。

  2. 与上面的第二个问题相关-需要从容器移动到容器的片段应该在xml中声明而不是,它们应该动态添加。否则你将无法改变它的容器,并会得到IllegalStateException:不能改变碎片异常的容器ID。相反,您可以在XML文件中定义容器并给它们一个ID,例如:

    <RelativeLayout
        android:id="@+id/fragmentContainer"
        android:layout_height="match_parent"
        android:layout_width="0dp"
        android:layout_weight="0.7" />
    

    ,然后再加上

    FragmentManager.beginTransaction().add(R.id.fragmentContainer, fragment);
    
  3. 如果你需要使用一些片段,首先看看FragmentManager是否有它-如果是,然后重用它-它将有它的状态保存作为奖励。查找fragment:

    private void MyFragment getMyFragment() {
        List<Fragment> fragments = getSupportFragmentManager().getFragments();
        if (fragments != null) {
            for (Fragment f : fragments) {
                // an example of search criteria
                if (f instanceof MyFragment) {
                    return (MyFragment) f;
                }
            }
        }
        return null;
    }
    

    如果是null,那么创建一个新的,否则你可以继续使用它,如果需要的话

  4. 重用一个片段的一种方法是把它放在另一个容器。你将需要一些努力,以不得到IllegalStateException:不能改变片段的容器ID。下面是我使用的代码和一些注释,以帮助理解它:

    private void moveFragment(MyFragment frag) {
        int targetContainer = R.id.myContainerLandscape;
        // first check if it is added to a correct place
        if (frag.isAdded()) {
            View v = frag.getView();
            if (v != null) {
                int id = ((ViewGroup) v.getParent()).getId();
                if (id == targetContainer) {
                    // already added to correct container, skip
                    return;
                }
            }
        }
        FragmentManager manager = getSupportFragmentManager();
        // Remove the fragment from its previous container first (done
        // here without check if added or nor, check if needed).
        // In order not to get 'Can't change container ID...' exception
        // we need to assure several things:
        //   1. its not hardcoded in xml - you can remove()
        //      fragment only if you have add()-ed before
        //   2. if this fragment is sitting deep in a backstack
        //      then you will still get the above mentioned exception.
        //      If the stack is fragA-fragB-fragC <-top then you get
        //      exception on moving fragment A. Need to clean the back
        //      stack first. HOWEVER, note that in that case you will
        //      lose the fragB and fragC together with their states!
        //      For that reason save them first - I will assume there is
        //      only one on top of current fragment to make code simpler.
        // before cleaning save the top fragment so that it is not destroyed
        OtherFragment temp = getOtherFragment(); // use function 'getMyFragment()' above
        // clean the backstack
        manager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        // remove the fragment from its current container
        manager.beginTransaction().remove(frag).commit();
        // call executePendingTransactions() for your changes to be available
        // right after this call, otherwise the previous 'commit()' just submits
        // the task to main thread and it will be done somewhere in the future
        manager.executePendingTransactions();
        if (getOtherFragment() == null && temp != null) {
            // Add the fragment we wanted to 'save' back to manager, this
            // time without any relation to backstack or container. Later
            // we will be able to find it using getOtherFragment() and the
            // fragment manager will be able to save/load its state for us.
            manager.beginTransaction().add(temp, null).commit();
            manager.executePendingTransactions();
        }
        // now add our fragment
        FragmentTransaction transaction = manager.beginTransaction();
        transaction.replace(targetContainer, frag);
        transaction.commit();
        manager.executePendingTransactions();
    }
    

这是我第一天认真处理片段的结果,至少我的任务是在我的应用程序中完成的。如果能从有经验的人那里得到一些关于这里有什么问题以及可以改进的地方的话,那就太好了。

相关内容

最新更新