2015-01-16 107 views
-1

UPDATE問題是我正在使用getFragmentManager()通過,而不是在帶有子片段的片段上使用getChildFragmentManager()。旋轉後「孤立」片段消失

問題依然存在:我將如何處理API < 17中的深層嵌套片段?

原始的問題

我的佈局結構如下。

fragment structure

與佈局main.xml中主要活動具有永久保留片段verytop,並且在的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在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; 
    } 
} 
+0

呼叫'setRetainInstance(真)'? –

+0

@TGMCians:我剛剛在ABottomInner上嘗試了setRetainInstance(true)。結果:如果您第一次點擊「顯示B」按鈕,然後「顯示A」按鈕,然後旋轉,應用程序崩潰NullPointerException。 – mathheadinclouds

+1

其實您正在另一個片段中呈現片段。所以你應該使用getChildFragmentManager()來進行交易。否則它會導致其他問題。 – Krish

回答

1

其實你是在另一個片段中呈現片段。所以你應該使用getChildFragmentManager()來進行交易。否則它會導致其他問題。

例如:對於添加/更換ABottom的用途內部getchildFragmentManager()

-1

得益於Krish發表評論,我想它了。

[1]在所有片段中,通過getChildFragmentManager替換getFragmentManager。

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

然後它工作。