2012年10月31日水曜日

Handling the Fragment Lifecycle

Handling the Fragment Lifecycle


Figure 3. The effect of the activity lifecycle on the fragment lifecycle.

Managing the lifecycle of a fragment is a lot like managing the lifecycle of an activity. Like an activity, a fragment can exist in three states:

Resumed
The fragment is visible in the running activity.
Paused
Another activity is in the foreground and has focus, but the activity in which this fragment lives is still visible (the foreground activity is partially transparent or doesn't cover the entire screen).
Stopped
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.

Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment'sonSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), oronActivityCreated(). For more information about saving state, see the Activities document.

The most significant difference in lifecycle between an activity and a fragment is how one is stored in its respective back stack. An activity is placed into a back stack of activities that's managed by the system when it's stopped, by default (so that the user can navigate back to it with the Back button, as discussed in Tasks and Back Stack). However, a fragment is placed into a back stack managed by the host activity only when you explicitly request that the instance be saved by calling addToBackStack() during a transaction that removes the fragment.

Otherwise, managing the fragment lifecycle is very similar to managing the activity lifecycle. So, the same practices for managing the activity lifecycle also apply to fragments. What you also need to understand, though, is how the life of the activity affects the life of the fragment.

Caution: If you need a Context object within your Fragment, you can call getActivity(). However, be careful to call getActivity() only when the fragment is attached to an activity. When the fragment is not yet attached, or was detached during the end of its lifecycle, getActivity() will return null.

Coordinating with the activity lifecycle

The lifecycle of the activity in which the fragment lives directly affects the lifecycle of the fragment, such that each lifecycle callback for the activity results in a similar callback for each fragment. For example, when the activity receives onPause(), each fragment in the activity receives onPause().

Fragments have a few extra lifecycle callbacks, however, that handle unique interaction with the activity in order to perform actions such as build and destroy the fragment's UI. These additional callback methods are:

onAttach()
Called when the fragment has been associated with the activity (the Activity is passed in here).
onCreateView()
Called to create the view hierarchy associated with the fragment.
onActivityCreated()
Called when the activity's onCreate() method has returned.
onDestroyView()
Called when the view hierarchy associated with the fragment is being removed.
onDetach()
Called when the fragment is being disassociated from the activity.

The flow of a fragment's lifecycle, as it is affected by its host activity, is illustrated by figure 3. In this figure, you can see how each successive state of the activity determines which callback methods a fragment may receive. For example, when the activity has received its onCreate() callback, a fragment in the activity receives no more than the onActivityCreated() callback.

Once the activity reaches the resumed state, you can freely add and remove fragments to the activity. Thus, only while the activity is in the resumed state can the lifecycle of a fragment change independently.

However, when the activity leaves the resumed state, the fragment again is pushed through its lifecycle by the activity.

Example


To bring everything discussed in this document together, here's an example of an activity using two fragments to create a two-pane layout. The activity below includes one fragment to show a list of Shakespeare play titles and another to show a summary of the play when selected from the list. It also demonstrates how to provide different configurations of the fragments, based on the screen configuration.

Note: The complete source code for this activity is available in FragmentLayout.java.

The main activity applies a layout in the usual way, during onCreate():

@Override  protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);        setContentView(R.layout.fragment_layout);  }

The layout applied is fragment_layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:orientation="horizontal"      android:layout_width="match_parent" android:layout_height="match_parent">        <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"              android:id="@+id/titles" android:layout_weight="1"              android:layout_width="0px" android:layout_height="match_parent" />        <FrameLayout android:id="@+id/details" android:layout_weight="1"              android:layout_width="0px" android:layout_height="match_parent"              android:background="?android:attr/detailsElementBackground" />    </LinearLayout>

Using this layout, the system instantiates the TitlesFragment (which lists the play titles) as soon as the activity loads the layout, while the FrameLayout (where the fragment for showing the play summary will go) consumes space on the right side of the screen, but remains empty at first. As you'll see below, it's not until the user selects an item from the list that a fragment is placed into the FrameLayout.

However, not all screen configurations are wide enough to show both the list of plays and the summary, side by side. So, the layout above is used only for the landscape screen configuration, by saving it at res/layout-land/fragment_layout.xml.

Thus, when the screen is in portrait orientation, the system applies the following layout, which is saved atres/layout/fragment_layout.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="match_parent" android:layout_height="match_parent">      <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"              android:id="@+id/titles"              android:layout_width="match_parent" android:layout_height="match_parent" />  </FrameLayout>

This layout includes only TitlesFragment. This means that, when the device is in portrait orientation, only the list of play titles is visible. So, when the user clicks a list item in this configuration, the application will start a new activity to show the summary, instead of loading a second fragment.

Next, you can see how this is accomplished in the fragment classes. First is TitlesFragment, which shows the list of Shakespeare play titles. This fragment extends ListFragment and relies on it to handle most of the list view work.

As you inspect this code, notice that there are two possible behaviors when the user clicks a list item: depending on which of the two layouts is active, it can either create and display a new fragment to show the details in the same activity (adding the fragment to the FrameLayout), or start a new activity (where the fragment can be shown).

public static class TitlesFragment extends ListFragment {      boolean mDualPane;      int mCurCheckPosition = 0;        @Override      public void onActivityCreated(Bundle savedInstanceState) {          super.onActivityCreated(savedInstanceState);            // Populate list with our static array of titles.          setListAdapter(new ArrayAdapter<String>(getActivity(),                  android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));            // Check to see if we have a frame in which to embed the details          // fragment directly in the containing UI.          View detailsFrame = getActivity().findViewById(R.id.details);          mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE;            if (savedInstanceState != null) {              // Restore last state for checked position.              mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);          }            if (mDualPane) {              // In dual-pane mode, the list view highlights the selected item.              getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);              // Make sure our UI is in the correct state.              showDetails(mCurCheckPosition);          }      }        @Override      public void onSaveInstanceState(Bundle outState) {          super.onSaveInstanceState(outState);          outState.putInt("curChoice", mCurCheckPosition);      }        @Override      public void onListItemClick(ListView l, View v, int position, long id) {          showDetails(position);      }        /**       * Helper function to show the details of a selected item, either by       * displaying a fragment in-place in the current UI, or starting a       * whole new activity in which it is displayed.       */      void showDetails(int index) {          mCurCheckPosition = index;            if (mDualPane) {              // We can display everything in-place with fragments, so update              // the list to highlight the selected item and show the data.              getListView().setItemChecked(index, true);                // Check what fragment is currently shown, replace if needed.              DetailsFragment details = (DetailsFragment)                      getFragmentManager().findFragmentById(R.id.details);              if (details == null || details.getShownIndex() != index) {                  // Make new fragment to show this selection.                  details = DetailsFragment.newInstance(index);                    // Execute a transaction, replacing any existing fragment                  // with this one inside the frame.                  FragmentTransaction ft = getFragmentManager().beginTransaction();                  ft.replace(R.id.details, details);                  ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);                  ft.commit();              }            } else {              // Otherwise we need to launch a new activity to display              // the dialog fragment with selected text.              Intent intent = new Intent();              intent.setClass(getActivity(), DetailsActivity.class);              intent.putExtra("index", index);              startActivity(intent);          }      }  }

The second fragment, DetailsFragment shows the play summary for the item selected from the list fromTitlesFragment:

public static class DetailsFragment extends Fragment {      /**       * Create a new instance of DetailsFragment, initialized to       * show the text at 'index'.       */      public static DetailsFragment newInstance(int index) {          DetailsFragment f = new DetailsFragment();            // Supply index input as an argument.          Bundle args = new Bundle();          args.putInt("index", index);          f.setArguments(args);            return f;      }        public int getShownIndex() {          return getArguments().getInt("index", 0);      }        @Override      public View onCreateView(LayoutInflater inflater, ViewGroup container,              Bundle savedInstanceState) {          if (container == null) {              // We have different layouts, and in one of them this              // fragment's containing frame doesn't exist.  The fragment              // may still be created from its saved state, but there is              // no reason to try to create its view hierarchy because it              // won't be displayed.  Note this is not needed -- we could              // just run the code below, where we would create and return              // the view hierarchy; it would just never be used.              return null;          }            ScrollView scroller = new ScrollView(getActivity());          TextView text = new TextView(getActivity());          int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,                  4, getActivity().getResources().getDisplayMetrics());          text.setPadding(padding, padding, padding, padding);          scroller.addView(text);          text.setText(Shakespeare.DIALOGUE[getShownIndex()]);          return scroller;      }  }

Recall from the TitlesFragment class, that, if the user clicks a list item and the current layout does not include the R.id.details view (which is where the DetailsFragment belongs), then the application starts theDetailsActivity activity to display the content of the item.

Here is the DetailsActivity, which simply embeds the DetailsFragment to display the selected play summary when the screen is in portrait orientation:

public static class DetailsActivity extends Activity {        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);            if (getResources().getConfiguration().orientation                  == Configuration.ORIENTATION_LANDSCAPE) {              // If the screen is now in landscape mode, we can show the              // dialog in-line with the list so we don't need this activity.              finish();              return;          }            if (savedInstanceState == null) {              // During initial setup, plug in the details fragment.              DetailsFragment details = new DetailsFragment();              details.setArguments(getIntent().getExtras());              getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();          }      }  }

Notice that this activity finishes itself if the configuration is landscape, so that the main activity can take over and display the DetailsFragment alongside the TitlesFragment. This can happen if the user begins theDetailsActivity while in portrait orientation, but then rotates to landscape (which restarts the current activity).

For more samples using fragments (and complete source files for this example), see the API Demos sample app available in ApiDemos (available for download from the Samples SDK component).

0 件のコメント:

コメントを投稿