问题
As reported by the Android guide, dual-pane can be achieved in two ways:
- Multiple fragments, one activity
- Multiple fragments, multiple activities
I am using the first case (the Android guide only explains the second case).
This is what happens on 7" tablets:
- rotating from landscape to portrait: only the single-pane fragment gets recreated
- rotating from portrait to landscape: all 3 fragments (single-pane, dual-pane-master, dual-pane-detail) get recreated
Question: why is the single-pane fragment (which I create programmatically, but using a FrameLayout defined in the layout as the container) get recreated on dual pane?
I am reporting below my implementation:
/layout/activity_main.xml:
<FrameLayout
android:id="@+id/single_pane"
android:layout_width="match_parent"
android:layout_height="match_parent" />
/layout-w900dp/activity_main.xml:
<LinearLayout
android:id="@+id/dual_pane"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment class="com.example.MasterFragment"
android:id="@+id/master_dual"
android:tag="MASTER_FRAGMENT_DUAL_PANE"
android:layout_width="@dimen/master_frag_width"
android:layout_height="match_parent"/>
<fragment class="com.example.DetailFragment"
android:id="@+id/detail_dual"
android:tag="DETAIL_FRAGMENT_DUAL_PANE"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
This is the onCreate
in the main activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDualPane = findViewById(R.id.dual_pane)!=null;
FragmentManager fm = getFragmentManager();
if (savedInstanceState==null) {
// this is a non-UI fragment I am using for data processing purposes
fm.beginTransaction().add(new NonUiFragment(), DATA_FRAGMENT).commit();
}
if (!mDualPane && fm.findFragmentById(R.id.single_pane)==null) {
fm.beginTransaction().add(R.id.single_pane, new MasterFragment(), MASTER_FRAGMENT_SINGLE_PANE).commit();
}
}
回答1:
I found it's much better to add the fragments in the code also for the dual pane.
So, instead of using the <fragment>
, also use the <FrameLayout>
also for the dual-pane XML.
/layout-w900dp/activity_main.xml:
<LinearLayout
android:id="@+id/dual_pane"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/master_dual"
android:layout_width="@dimen/master_frag_width"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/detail_dual"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
In this way, you can use just one instance of the masterFragment and of the DetailFragment, so you don't fall into the problem of having multiple instances of the same fragment.
In order to do this, in the OnCreate
you need to add the fragments to the container, detaching from the old container:
mDualPane = findViewById(R.id.dual_pane)!=null;
if (savedInstanceState!=null) {
mLastSinglePaneFragment = savedInstanceState.getString("lastSinglePaneFragment");
}
FragmentManager fm = getSupportFragmentManager();
if (!mDualPane && fm.findFragmentById(R.id.single_pane)==null) {
MasterFragment masterFragment = getDetatchedMasterFragment(false);
fm.beginTransaction().add(R.id.single_pane, masterFragment, MASTER_FRAGMENT).commit();
if (mLastSinglePaneFragment==DETAIL_FRAGMENT) {
openSinglePaneDetailFragment();
}
}
if (mDualPane && fm.findFragmentById(R.id.master_dual)==null) {
MasterFragment masterFragment = getDetatchedMasterFragment(true);
fm.beginTransaction().add(R.id.master_dual, masterFragment, MASTER_FRAGMENT).commit();
}
if (mDualPane && fm.findFragmentById(R.id.detail_dual)==null) {
DetailFragment detailFragment = getDetatchedDetailFragment();
fm.beginTransaction().add(R.id.detail_dual, detailFragment, DETAIL_FRAGMENT).commit();
}
using these functions:
public static final String MASTER_FRAGMENT = "MASTER_FRAGMENT";
public static final String DETAIL_FRAGMENT = "DETAIL_FRAGMENT";
private MasterFragment getDetatchedMasterFragment(boolean popBackStack) {
FragmentManager fm = getSupportFragmentManager();
MasterFragment masterFragment = getSupportFragmentManager().findFragmentByTag(MASTER_FRAGMENT);
if (masterFragment == null) {
masterFragment = new MasterFragment();
} else {
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
fm.beginTransaction().remove(masterFragment).commit();
fm.executePendingTransactions();
}
return masterFragment;
}
private DetailFragment getDetatchedDetailFragment() {
FragmentManager fm = getSupportFragmentManager();
DetailFragment detailFragment = getSupportFragmentManager().findFragmentByTag(DETAIL_FRAGMENT);
if (detailFragment == null) {
detailFragment = new DetailFragment();
} else {
fm.beginTransaction().remove(detailFragment).commit();
fm.executePendingTransactions();
}
return detailFragment;
}
private void openSinglePaneDetailFragment() {
FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
DetailFragment detailFragment = getDetatchedDetailFragment();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.replace(R.id.single_pane, detailFragment, DETAIL_FRAGMENT);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
回答2:
When you rotate, the currently active fragments will be saved by the FragmentManager and used to recreate fragments automatically when the new Activity is created. You can prevent recreation by not passing the savedInstanceState to the super method. E.g. super.onCreate(null);
.
Alternatively if you need to restore state using the FragmentActivity.onCreate(savedInstanceState) method (which calls FragmentManager.restoreAllState() —see https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/FragmentManager.java#L1759), you can lookup your fragment tag and remove it manually in your onCreate. This is the case since you have a non-ui Fragment you want to restore. The restoration of retained fragments also depends on the call to FragmentActivity.onCreate(savedInstanceState) with saveInstanceState != null.
The recreation happens because usually you want to keep the active fragment around (and possibly add a second detail pane in the case of tablets).
if (mDualPane) {
Fragment singlePane = getFragmentManager().findFragmentByTag(MASTER_FRAGMENT_SINGLE_PANE);
if (singlePane != null)
getFragmentManager().beginTransaction().remove(fragment).commit();
}
来源:https://stackoverflow.com/questions/25430283/android-master-detail-flow-dual-pane-using-1-activity