简体   繁体   中英

Why does rotation cause Android fragment replacement to fail?

I have put together a simple program that uses fragment replacement with a single activity and two fragments. One fragment has a button, that when pressed, replaces the fragment with a second fragment.

This works as expected if the application is started and the button is clicked. 第一片段片段替换成功

If the application is started, the device is rotated (either to landscape or continuing back to portrait), and the button is clicked, then the fragment replacement fails with both fragments being displayed simultaneously. 碎片更换失败

What is going on here? Is there fragment lifecycle issue I am failing to address?

MainActivity.java

package edu.mindlab.fragmenttest;

import android.app.FragmentTransaction;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;


public class MainActivity extends ActionBarActivity{
    private MainActivityFragment startingFragment;
    private ReplacementFragment replacementFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startingFragment = new MainActivityFragment();
        replacementFragment = new ReplacementFragment();

        FragmentTransaction t = getFragmentManager().beginTransaction();
        t.add(R.id.fragment_container, startingFragment, "start").commit();
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    public void replaceFragment(View view){
        if(!replacementFragment.isAdded()) {
            FragmentTransaction t = getFragmentManager().beginTransaction();
            t.replace(R.id.fragment_container, replacementFragment, "replacementTest").commit();
        }
    }
}

MainActivityFragment.java

package edu.mindlab.fragmenttest;

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

public class MainActivityFragment extends Fragment {
    public MainActivityFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);

        return view;
    }
}

ReplacementFragment.java

package edu.mindlab.fragmenttest;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class ReplacementFragment extends Fragment {

    public static ReplacementFragment newInstance() {
        ReplacementFragment fragment = new ReplacementFragment();
        return fragment;
    }

    public ReplacementFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_replacement, container, false);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

}

activity_main.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:id="@+id/fragment_container">
</FrameLayout>

fragment_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="@dimen/activity_horizontal_margin"
            android:paddingRight="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            android:paddingBottom="@dimen/activity_vertical_margin"
            tools:context=".MainActivityFragment">

<TextView
    android:text="@string/start_string"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/textView"/>

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/replacement_fragment"
    android:id="@+id/button"
    android:layout_below="@+id/textView"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_marginTop="56dp"
    android:onClick="replaceFragment"/>


</RelativeLayout>

fragment_replacement.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:tools="http://schemas.android.com/tools"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         tools:context="edu.mindlab.fragmenttest.ReplacementFragment">

<TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:text="@string/replacement_fragment"/>

</FrameLayout>

The answer is simple. UI state is retained across screen rotations meaning if you do nothing the screen would still show the second fragment once you rotate. Since you add the first fragment in your Activity onCreate method it will be added to what is already shown hence the two fragments overlap.

You could do something like this:

FragmentManager mgr = getFragmentManager();
if (mgr.findFragmentByTag("start") == null &&
    mgr.findFragmentByTag("replacementTest") == null) {

    FragmentTransaction t = mgr.beginTransaction();
    t.add(R.id.fragment_container, startingFragment, "start").commit();             

}

@Emanuel is correct that the UI state is retained across screen rotations. However what's happening is simply that your Activity's onCreate is always called on rotation, which always adds your startingFragment

The solution is simple. Just add a condition in your onCreate as so to handle the case when it's called as part of the activity lifecycle:

if (savedInstanceState == null)
        {
            FragmentTransaction t = getFragmentManager().beginTransaction();
            t.add(R.id.fragment_container, startingFragment, "start").commit();
        }

You might want to consider doing

t.replace(R.id.fragment_container, startingFragment, "start").commit();

though instead of

t.add(R.id.fragment_container, startingFragment, "start").commit();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM