UPDATE問題是我正在使用getFragmentManager()通過,而不是在帶有子片段的片段上使用getChildFragmentManager()。旋轉後「孤立」片段消失
問題依然存在:我將如何處理API < 17中的深層嵌套片段?
原始的問題
我的佈局結構如下。
與佈局main.xml中主要活動具有永久保留片段verytop,並且在的FrameLayout,無論是片段A(A.XML)或片段B(B.XML)被示出。您可以通過單擊按鈕在A和B之間切換(在abottom_inner中有一個可以調出B的按鈕,而在b中有一個按鈕可以使A再次返回。)
只要我不旋轉,一切正常。此外,如果我留在片段A(不要點擊按鈕),並旋轉,它也可以正常工作。但是,如果我向B旋轉,再回到A,然後旋轉,abottom_inner將不可見。
這是它的外觀和啓動(肖像模式,僅示出了上部)
按壓鍵 「節目B」 之後:
壓制後「節目A「,它再次出現在第一個屏幕截圖中。然後,旋轉爲橫向後,我得到這個
這裏是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在ABottom的onCreateView之後被調用。當它不(在旋轉之後,如果你之前點擊了按鈕),順序顛倒。所以我的猜測是,在顛倒順序的情況下,ABottomInner變成了「孤兒」 - 它依賴於之前調用的ABottom的onCreateView,如果不是這樣,它就無法正確地附加自身。任何人都可以證實或駁斥我的猜測嗎?另外,是否有任何關於旋轉過程中調用onCreateView方法的順序的規則,還是隻是隨機的?它會出現這種情況,因爲如果您在啓動後立即旋轉,則順序不會顛倒,並且片段保持可見狀態。
我有一個醜陋的醜陋解決方法,如果您選中複選框,它將被激活。然後,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;
}
}
呼叫'setRetainInstance(真)'? –
@TGMCians:我剛剛在ABottomInner上嘗試了setRetainInstance(true)。結果:如果您第一次點擊「顯示B」按鈕,然後「顯示A」按鈕,然後旋轉,應用程序崩潰NullPointerException。 – mathheadinclouds
其實您正在另一個片段中呈現片段。所以你應該使用getChildFragmentManager()來進行交易。否則它會導致其他問題。 – Krish