后堆栈中的配置更改片段现在正在共享 FrameLayout?

Posted

技术标签:

【中文标题】后堆栈中的配置更改片段现在正在共享 FrameLayout?【英文标题】:after configuration change fragment from backstack is now sharing the FrameLayout? 【发布时间】:2013-05-20 09:28:05 【问题描述】:

应用问题:

当方向改变时,应用会遇到以下问题:

FragmentA 和 FragmentC 现在都占用 FrameLayout 容器。

工作原理:一切都按我的意愿工作...在旋转屏幕之前。

活动简介:

EditActivity 目的:编辑集合和项目字段。

此活动以编程方式创建的片段:

FragmentA - 用于编辑集合字段的片段 FragmentB - 集合中项目的 ListFragment FragmentC - 用于编辑项目字段的片段。

初始布局:FragmentA 位于 FragmentB 之上,每个都在自己的 FrameLayouts 中。

当用户点击 FragmentB 的列表视图项目时:将 FragmentA 替换为 FragmentC 以允许用户编辑该项目的字段。现在 FragmentC 位于 FragmentB 之上。

这似乎是一个非常简单的概念:活动的顶部用于编辑整个集合的属性或集合中的单个项目。我觉得我在布局上没有做任何奇妙的事情,所以我有点困惑,手机(模拟器)的简单旋转会导致这些问题,我花了这么多时间试图解决。

为什么android Fragment Guide example 对我不起作用:他们的示例与我正在做的非常相似,但他们的详细信息片段要么在新活动中打开,要么在当前活动中的自己的框架中打开,但他们没有进行任何片段交换,因此我无法收集他们将如何使用 onSaveIstanceState 来保留可见的片段,然后在 onCreate 中使用该信息来重新创建在方向更改之前存在的 UI。

编辑:通过将 listfragment 放入 XML 中解决了一个问题,这解决了永久旋转的“加载...”问题。

【问题讨论】:

【参考方案1】:

解决了。哦,我走过的兔子洞……无论如何,如果您遇到这样的问题,需要考虑以下几点:

最终我不用在onSaveInstanceState(Bundle outState)写任何代码了。 最终我不必考虑处理onSaveInstanceState 中的backstack 或处理活动的onCreate。 第一次以编程方式将片段“添加”到 FrameLayout 时,请使用 replace 而不是 `add' - 这可能是我的问题的根源之一。 在 onCreate 中检查 savedInstanceState 的 bundle 是否为 null,if(savedInstanceState == null),如果是,那么我知道该活动之前没有因配置更改而被拆除,所以在这里我构建了应该在活动时显示的片段启动。以编程方式在其他地方实现的其他片段(即,晚于活动的onCreate()),它们不属于if,它们属于elseelse onSaveInstanceState != null 我知道这个东西不为空只有一个原因,因为系统在 onSaveInstanceState(Bundle outState) 中创建了一个名为 outState 的包,并将其放入活动的 onCreate 方法中,我现在可以在其中获取我的垃圾。所以在这里我知道了几件事:
    确定我在活动的onCreate 中创建的片段仍然是活动的一部分(我没有分离或销毁它们),但是,我不能对通过用户的操作使片段变得栩栩如生,这些片段可能当前(在定向又名配置更改时)附加到活动,也可能不附加到活动。 这是一个if-this-thing-is-attached 子句的好地方。我最初搞砸的一件事是我没有给我所有以编程方式添加的片段一个标签;给所有以编程方式添加的片段标签。然后我可以找出 savedInstanceState 包是否包含带有 savedInstanceState.containsKey(MY_FRAG_TAG)getFragmentManager().findFragmentByTag(MY_FRAG_TAG) 的密钥

这里是活动的 onCreate(简化):

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_edit);

    // ...omitted code...

    if(savedInstanceState == null)                     
        // create fragment for collection edit buttons
        editCollection = FragmentA.newInstance(someVariable);               

        // programmatically add fragment to ViewGroup
        getFragmentManager().beginTransaction().replace(R.id.edit_topFrame, editCollection, EDIT_COLLECTIONS_TAG).commit();


    
    // else there be stuff inside the savedInstanceState bundle
    else
        // fragments that will always be in the savedInstanceState bundle
        editCollectionFragment = (FragmentA)getFragmentManager().findFragmentByTag(EDIT_COLLECTIONS_TAG);

        // fragments that may not be in the bundle
        if(savedInstanceState.containsKey(EDIT_ITEM_TAG))
            editItemFragment = (FragmentC)getFragmentManager().getFragment(savedInstanceState, EDIT_ITEM_TAG);              
        

    

    // This fragment is NOT programmatically added, ie, it is statically found in an XML file.
    // Hence, the system will take care of preserving this fragment on configuration changes.
    listFrag = (ListViewFragment)getFragmentManager().findFragmentById(R.id.ListFragment);


    // create adapter
    adapter = new EditCursorAdapter(this, null);

    // set list fragment adapter
    listFrag.setListAdapter(adapter);

    // prepare the loader
    getLoaderManager().initLoader(LOADER_ID, null, this);

以及 Activity 的列表片段监听器,其中 FragmentC 被交换为 FragmentA:

// listfragment listener
@Override
public void listFragListener(Cursor cursor) 

    // checking backstack size
    Log.d(TAG, SCOPE +"backstack size: "+getFragmentManager().getBackStackEntryCount());

    // With each listview click there should be only one item in the backstack.
    getFragmentManager().popBackStack();

    // create new fragment
    editItemFragment = FragmentC.newInstance(cursor);

    // programmatically add new fragment
    FragmentTransaction ft = getFragmentManager().beginTransaction();
    ft.replace(R.id.edit_topFrame, editItemFragment, EDIT_ITEM_TAG);
    ft.addToBackStack("pop all of these");  // was testing different ways of popping
    ft.commit();

    // interesting: this reports the same value as the first log in this method.
    // ...clearly addToBackStack(null).commit() doesn't populate the backstack immediately?
    Log.d(TAG, SCOPE +"backstack size: "+getFragmentManager().getBackStackEntryCount());        

而 onSaveInstanceState 就像一只松鸦一样赤身裸体:

    @Override
protected void onSaveInstanceState(Bundle outState) 
    super.onSaveInstanceState(outState);        


总结:我的活动完全按照我的意愿运作。

现在,如果我有一堆添加的片段,那么我可能会以更程序化的方式处理它们,而不是硬编码if(savedInstanceState.contains(*hard coded key*)。我对此进行了一些测试,但无法证明其功效,但是对于那里的人来说,这可能会激发您对您可以做什么的想法:

    制作一组私有的添加片段:

    // Collection of Frag Tags
    private Set<String> AddedFragmentTagsSet = new HashSet<String>();
    

    onAttachFragment 中执行以下操作:

    @Override
    public void onAttachFragment(Fragment fragment) 
    super.onAttachFragment(fragment);   
    
    // logging which fragments get attached and when
    Log.d(TAG, SCOPE +"attached fragment: " +fragment.toString());
    
    // NOTE: XML frags have not frigg'n tags
    
    // add attached fragment's tag to set of tags for attached fragments
    AddedFragmentTagsSet.add(fragment.getTag());
    
    // if a fragment has become detached remove its tag from the set
    for(String tag : AddedFragmentTagsSet)
        if(getFragmentManager().findFragmentByTag(tag).isDetached())
            AddedFragmentTagsSet.remove(tag);
        
        Log.d(TAG, SCOPE +"contents of AddedFragmentTagsSet: " +tag);
    
    
    

    然后在活动的onCreatesavedInstanceState 子句中:

    @Override
    protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_edit);
    
    // ...omitted code...
    
    if(savedInstanceState == null)                     
        // create fragment for collection edit buttons
        editCollection = FragmentA.newInstance(someVariable);               
    
        // programmatically add fragment to ViewGroup
        getFragmentManager().beginTransaction().replace(R.id.edit_topFrame, editCollection, EDIT_COLLECTIONS_TAG).commit();
    
    
    
    // else there be stuff inside the savedInstanceState bundle
    else
        // fragments that will always be in the savedInstanceState bundle
        editCollectionFragment = (FragmentA)getFragmentManager().findFragmentByTag(EDIT_COLLECTIONS_TAG);
    
        //////////// find entries that are common to AddedFragmentTagsSet & savedInstanceState's set of keys ///////////
    
        Set<String> commonKeys = savedInstanceState.keySet();           
        commonKeys.retainAll(AddedFragmentTagsSet);
    
        for(String key : commonKeys)
            editItemFragment = FragmentC)getFragmentManager().getFragment(savedInstanceState, key);             
    
                   
    
    
    

...但这未经测试,仅用于激发想法;在试图找出我的活动处理配置更改的问题时,我确实在这个方向上磕磕绊绊,并认为它可能会为合适的人带来成果;虽然最终,很明显,这次我找到了一种更简单的方法来解决我的问题。

【讨论】:

除了重新配置之外,是否存在bundle savedInstanceState不为空的情况?另外,有没有办法检索和使用片段的重新创建版本,而不是创建另一个实例并替换旧的?

以上是关于后堆栈中的配置更改片段现在正在共享 FrameLayout?的主要内容,如果未能解决你的问题,请参考以下文章

导航组件替换/更改后台堆栈

如何使用导航组件切换到不同后堆栈中的其他片段?

配置更改后片段丢失过渡动画

任何更改后的 Android Firebase 数据库活动/片段正在关闭

导航到Android中的另一个片段后如何清除导航堆栈

方向/配置更改后如何维护 ListView 片段状态?