“孤儿”片段在旋转后消失

时间:2015-01-16 13:39:07

标签: android android-fragments rotation

UPDATE 问题在于我使用的是getFragmentManager(),而不是在带有子片段的片段上使用getChildFragmentManager()。

问题仍然存在:我如何处理API中的深层嵌套片段< 17?

原始问题

我的布局结构如下。

fragment structure

布局main.xml的主要活动具有永久保留的片段,并且在FrameLayout中显示片段A(a.xml)或片段B(b.xml)。您可以通过单击按钮在A和B之间切换(在abottom_inner中有一个按钮会弹出B,而在b中有一个按钮会再次带回A.)

只要我不旋转,一切正常。此外,如果我留下片段A(不要点击按钮),并旋转,它也可以正常工作。但如果我转向B,再次回到A,然后旋转,abottom_inner是不可见的。

这是它的外观和启动方式(纵向模式,仅显示上部)

a

按下按钮"显示B":

b

按下"显示A"后,它再次显示在第一个屏幕截图中。然后,在旋转到横向之后,我得到了这个

invisible fragment

这是logcat输出

***(start)
Main:          MAIN ONCREATE 
Main:          adding A (happens only at startup)
A:             onCreateView
A:             added atop
A:             added abottom
ATop:          onCreateView
ABottom:       onCreateView
ABottom:       added aBottomInner
ABottomInner:  onCreateView
ABottomInner:  a bottom inner button clicked
***(switch to B)
Main:          replacing A with B
B:             onCreateView
B:             b button clicked
***(switch back to A)
Main:          replacing B with A
A:             onCreateView
A:             added atop
A:             added abottom
ATop:          onCreateView
ABottom:       onCreateView
ABottom:       added aBottomInner
ABottomInner:  onCreateView
***(rotate to landscape)
Main:          MAIN ONCREATE 
Main:          content exists
A:             onCreateView
A:             atop already exists
A:             abottom already exists
ABottomInner:  onCreateView
ABottom:       onCreateView
ABottom:       aBottomInner already exists
ATop:          onCreateView

查看logcat,我猜测行为的原因是为每个子片段调用onCreateView方法的顺序。当它工作时(在启动时和按钮点击后),ABottomInner的onCreateView在ABOTom的onCreateView之后被调用。如果它没有(旋转之后,如果您之前点击了按钮),则顺序相反。所以我的猜测是,在逆序的情况下,ABottomInner变成了孤儿" - 它依赖于之前调用的ABottom的onCreateView,如果不是这样,它就无法正确附加自身。任何人都可以确认或驳斥我的猜测吗?另外,是否有关于在旋转期间调用onCreateView方法的顺序的规则,还是只是随机的?它看起来是这样,因为如果你在启动后立即旋转,顺序不会反转,并且片段保持可见。

我有一个脏的丑陋的解决方法,如果你检查CheckBox,它会被激活。然后,将重新创建ABottomInner,即使它已经存在。然后,它按预期工作。然后将调用ABottomInner的onCreateView两次。我无法想象这是做这件事的正确方法。什么是确保abottom_inner不会消失的正确方法?

这是完整的代码。

main.xml中

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <fragment
        android:id="@+id/VeryTopFragment"
        android:name="com.example.nestedfrags.VeryTop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <FrameLayout
        android:id="@+id/contentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />

</LinearLayout>

verytop.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@color/lime"
>

    <TextView
        android:id="@+id/veryTopTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=" ??? "
        android:layout_gravity="center_horizontal"
    />

</LinearLayout>

A.XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >


    <FrameLayout
        android:id="@+id/aTop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/orange"
    />

    <FrameLayout
        android:id="@+id/aBottom"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/navy"
    />

</LinearLayout>

atop.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@color/yellow"
>

    <TextView
        android:id="@+id/atopTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="---"
    />

</LinearLayout>

abottom.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/iamMain"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/silver">

    <CheckBox
        android:id="@+id/checkBox1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox" />

    <FrameLayout
        android:id="@+id/abottomFL"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
    </FrameLayout>

</FrameLayout>

abottom_inner.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/white"
>

    <TextView
        android:id="@+id/aBottomInnerTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:textColor="@color/black"
        android:text=" ??? "
    />

    <Button
        android:id="@+id/aBotInnerButton"
        android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="show B" />
</LinearLayout>

B.XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >


    <Button
        android:id="@+id/bButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="show A" />

</LinearLayout>

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="white">#FFFFFF</color>
    <color name="yellow">#FFFF00</color>
    <color name="silver">#C0C0C0</color>
    <color name="lime">#00FF00</color>
    <color name="navy">#000080</color>
    <color name="black">#000000</color>
    <color name="orange">#F7931E</color>
</resources>

MainActivity.java

package com.example.nestedfrags;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {

    private static final String TAG = "Main";

    public VeryTop veryTop;

    Fragment contentFragment;

    public void showB(){
        FragmentManager fm = getFragmentManager();
        contentFragment = fm.findFragmentById(R.id.contentFragment);
        FragmentTransaction ft = fm.beginTransaction();
        B b = new B();
        // wouldn't it be nice if android were smart enough to remove "dependents" by itself?!
        ft.remove(fm.findFragmentById(R.id.aTop));
        ft.remove(fm.findFragmentById(R.id.aBottom));
        ft.remove(fm.findFragmentById(R.id.abottomFL));
        if (contentFragment == null){
            Log.i(TAG, "adding B");
            ft.add(R.id.contentFragment, b, "B").commit();
        } else {
            Log.i(TAG, "replacing A with B");
            ft.replace(R.id.contentFragment, b, "B").commit();
        }
    }
    public void showA(){
        FragmentManager fm = getFragmentManager();
        contentFragment = fm.findFragmentById(R.id.contentFragment);
        FragmentTransaction ft = fm.beginTransaction();
        if (contentFragment == null){
            Log.i(TAG, "adding A");
            ft.add(R.id.contentFragment, new A(), "A").commit();
        } else {
            Log.i(TAG, "replacing B with A");
            ft.replace(R.id.contentFragment, new A(), "A").commit();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, " MAIN ONCREATE ");
        setContentView(R.layout.main);
        FragmentManager fm = getFragmentManager();
        contentFragment = fm.findFragmentById(R.id.contentFragment);
        veryTop = (VeryTop)fm.findFragmentById(R.id.VeryTopFragment);
        if (contentFragment == null){
            Log.i(TAG, "adding A (happens only at startup)");
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(R.id.contentFragment, new A(), "A").commit();
        } else {
            Log.i(TAG, "content exists");
        }

    }
}

VeryTop.java

package com.example.nestedfrags;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class VeryTop extends Fragment {

    public boolean forceInnerRecreation = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
    public void updateTextView(View v){
        TextView tv = (TextView)v.findViewById(R.id.veryTopTV);
        tv.setText(" very top - forceInnerRecreation is: " + forceInnerRecreation);
    }
    public void setForceInnerRecreation(boolean value){
        forceInnerRecreation = value;
        updateTextView(getView());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.verytop, container, false);
        updateTextView(v);
        return v;
    }

}

A.java

package com.example.nestedfrags;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class A extends Fragment {

    private static final String TAG = "A";

    private void incarnateTop(View v, FragmentManager fm){
        int layoutId = R.id.aTop;
        ATop fragment = (ATop)fm.findFragmentById(layoutId);
        boolean fragmentWasNull = false;
        if (fragment == null){
            fragment = new ATop();
            fragmentWasNull = true;
        }
        fragment.text = " == A TOP == "; 
        if (fragmentWasNull){
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(layoutId, fragment, "atop").commit();
            Log.i(TAG, "added atop");
        } else {
            Log.i(TAG, "atop already exists");
        }
    }
    private void incarnateBottom(View v, FragmentManager fm){
        int layoutId = R.id.aBottom;
        ABottom fragment = (ABottom)fm.findFragmentById(layoutId);
        boolean fragmentWasNull = false;
        if (fragment == null){
            fragment = new ABottom();
            fragmentWasNull = true;
        }

        if (fragmentWasNull){
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(layoutId, fragment, "abottom").commit();
            Log.i(TAG, "added abottom");
        } else {
            Log.i(TAG, "abottom already exists");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.a, container, false);
        Log.i(TAG, "onCreateView");
        FragmentManager fm = getFragmentManager();
        incarnateTop(v, fm);
        incarnateBottom(v, fm);
        return v;
    }

}

ATop.java

package com.example.nestedfrags;

import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class ATop extends Fragment {

    private static final String TAG = "ATop";

    String text = "invalid";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.atop, container, false);
        Log.i(TAG, "onCreateView");
        FragmentManager fm = getFragmentManager();
        TextView tv = (TextView)v.findViewById(R.id.atopTV);
        tv.setText(text);
        return v;
    }

}

ABottom.java

package com.example.nestedfrags;

import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.FrameLayout;

public class ABottom extends Fragment {

    private static final String TAG = "ABottom";


    private void incarnateInner(View v, FragmentManager fm){
        int layoutId = R.id.abottomFL;
        FrameLayout container = (FrameLayout)v.findViewById(layoutId);
        container.setPadding(0, 200, 0, 0);
        ABottomInner fragment = (ABottomInner)fm.findFragmentById(layoutId);
        boolean fragmentWasNull = false;
        if (fragment == null){
            fragment = new ABottomInner();
            fragmentWasNull = true;
        }
        fragment.text = " -- a bottom inner -- ";
        if (fragmentWasNull){
            FragmentTransaction ft = fm.beginTransaction();
            ft.add(layoutId, fragment, "aBottomInner").commit();
            Log.i(TAG, "added aBottomInner");
        } else {
            if (forceInnerRecreation()){
                FragmentTransaction ft = fm.beginTransaction();
                fragment = new ABottomInner();
                fragment.text = " using brute workaround ";
                ft.replace(layoutId, fragment, "aBottomInner").commit();
                Log.i(TAG, "putting in fresh copy of aBottomInner");
            } else {
                Log.i(TAG, "aBottomInner already exists");
            }
        }
    }
    private boolean forceInnerRecreation(){
        MainActivity main = (MainActivity)getActivity();
        return main.veryTop.forceInnerRecreation;
    }
    private void setForceInnerRecreation(boolean value){
        MainActivity main = (MainActivity)getActivity();
        main.veryTop.setForceInnerRecreation(value);
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.abottom, container, false);
        Log.i(TAG, "onCreateView");
        FragmentManager fm = getFragmentManager();
        CheckBox cb = (CheckBox)v.findViewById(R.id.checkBox1);
        cb.setChecked(forceInnerRecreation());
        OnCheckedChangeListener cbListener = new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setForceInnerRecreation(isChecked);
                if (isChecked){
                    Log.i(TAG, "setting brute workaround ON");
                } else {
                    Log.i(TAG, "setting brute workaround OFF");
                }
            }
        };
        cb.setOnCheckedChangeListener(cbListener);
        incarnateInner(v, fm);
        return v;
    }

}

ABottomInner.java

package com.example.nestedfrags;

import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

public class ABottomInner extends Fragment {

    private static final String TAG = "ABottomInner";

    String text = "invalid";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.abottom_inner, container, false);
        Log.i(TAG, "onCreateView");
        TextView tv = (TextView)v.findViewById(R.id.aBottomInnerTV);
        tv.setText(text);
        Button btn = (Button)v.findViewById(R.id.aBotInnerButton);
        OnClickListener listener = new OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "a bottom inner button clicked");
                MainActivity main = (MainActivity)getActivity();
                main.showB();
            }
        };
        btn.setOnClickListener(listener);
        return v;
    }
}

B.java

package com.example.nestedfrags;

import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.Button;

public class B extends Fragment {

    private static final String TAG = "B";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.i(TAG, "onCreateView");
        View v = inflater.inflate(R.layout.b, container, false);
        Button bButton = (Button)v.findViewById(R.id.bButton);
        OnClickListener listener = new OnClickListener(){
            @Override
            public void onClick(View v) {
                Log.i(TAG, "b button clicked");
                MainActivity main = (MainActivity)getActivity();
                main.showA();
            }
        };
        bButton.setOnClickListener(listener);
        return v;
    }
}

2 个答案:

答案 0 :(得分:1)

实际上你正在另一个片段中展示Fragment。因此,您应该使用getChildFragmentManager()进行事务处理。另外,它会导致其他问题。

例如:在A中添加/替换ABottom使用getchildFragmentManager()

答案 1 :(得分:-1)

感谢Krish的评论,我明白了。

所有片段中的

[1],用getChildFragmentManager替换getFragmentManager。

[2]删除ft.remove形式的3行(fm.findFragmentById(R.id。...)); 来自MainActivity

然后它有效。