1. Fragments
A Fragment represents a behavior or a portion of user interface in an Activity.
You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a
fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove
while the activity is running (sort of like a "sub activity" that you can reuse in different activities).
A fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle
2. Design Philosophy
For example, a news application can use one fragment to show a list of articles on the left and another fragment to display an article on the
right—both fragments appear in one activity, side by side, and each fragment has its own set of lifecycle callback methods and handle
their own user input events. Thus, instead of using one activity to select an article and another activity to read the article, the user can
select an article and read it all within the same activity, as illustrated in the tablet layout in figure 1.
3. Creating a Fragment
There are also a few subclasses that you might want to extend, instead of the base Fragment
class:
-
Displays a floating dialog. Using this class to create a dialog is a good alternative to using the dialog helper methods in the
Activity
class, because you can incorporate a fragment dialog into the back stack of fragments managed by the activity, allowing the user to return to a dismissed fragment. -
Displays a list of items that are managed by an adapter (such as a
SimpleCursorAdapter
), similar toListActivity
. It provides several methods for managing a list view, such as theonListItemClick()
callback to handle click events. -
Displays a hierarchy of
Preference
objects as a list, similar toPreferenceActivity
. This is useful when creating a "settings" activity for your application.
DialogFragment
ListFragment
PreferenceFragment
4. Adding a user interface
To provide a layout for a fragment, you must implement the onCreateView()
callback method, which the Android system calls when it's time
for the fragment to draw its layout. Your implementation of this method must return a View
that is the root of your fragment's layout.
To return a layout from onCreateView()
, you can inflate it from a layout resource defined in XML. To help you do so, onCreateView()
provides
a LayoutInflater
object
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public static class ExampleFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); } }
The container
parameter passed to onCreateView()
is the parent ViewGroup
(from the activity's layout) in which your fragment layout will be
inserted.
The savedInstanceState
parameter is a Bundle
that provides data about the previous instance of the fragment, if the fragment is being resumed
The inflate()
method takes three arguments:
<1>The resource ID of the layout you want to inflate
<2>The ViewGroup
to be the parent of the inflated layout. Passing the container
is important in order for the system to apply layout
parameters to the root view of the inflated layout, specified by the parent view in which it's going.
<3>A boolean indicating whether the inflated layout should be attached to the ViewGroup
(the second parameter) during inflation.
(In this case, this is false because the system is already inserting the inflated layout into the container
—passing true would create a
redundant view group in the final layout.)
5. Adding a fragment to an activity
Usually, a fragment contributes a portion of UI to the host activity, which is embedded as a part of the activity's overall view hierarchy.
There are two ways you can add a fragment to the activity layout:
<1>Declare the fragment inside the activity's layout file
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.news.ArticleListFragment" android:id="@+id/list" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.news.ArticleReaderFragment" android:id="@+id/viewer" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>
The android:name attribute in <fragment> specifies the Fragment class to instantiate in the layout
When the system creates this activity layout, it instantiates each fragment specified in the layout and calls the onCreateView()
method for each
one, to retrieve each fragment's layout. The system inserts the View
returned by the fragment directly in place of the <fragment>
element.
<2>Or progammatically add the fragment to an existing ViewGroup
At any time while your activity is running, you can add fragments to your activity layout. You simply need to specify a ViewGroup
in which to
place the fragment. To make fragment transactions in your activity (such as add, remove, or replace a fragment), you must use APIs
from FragmentTransaction
. You can get an instance of FragmentTransaction
from your Activity
like this:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
You can then add a fragment using the add()
method, specifying the fragment to add and the view in which to insert it. For example:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit();
6. Managing Fragments
To manage the fragments in your activity, you need to use FragmentManager
. To get it, call getFragmentManager()
from your activity.
7. Performing Fragment Trasactions
A great feature about using fragments in your activity is the ability to add, remove, replace, and perform other actions with them, in response
to user interaction. Each set of changes that you commit to the activity is called a transaction and you can perform one using APIs in
FragmentTransaction
. You can also save each transaction to a back stack managed by the activity, allowing the user to navigate backward
through the fragment changes (similar to navigating backward through activities).
Each transaction is a set of changes that you want to perform at the same time. You can set up all the changes you want to perform for a
given transaction using methods such as add()
, remove()
, and replace()
. Then, apply the transaction to the activity, you must call commit()
.
For example, here's how you can replace one fragment with another, and preserve the previous state in the back stack:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
// Create new fragment and transaction Fragment newFragment = new ExampleFragment(); FragmentTransaction transaction = getFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit();
8. Communicating with the Activity
Although a Fragment
is implemented as an object that's independent from an Activity
and can be used inside multiple activities, a given
instance of a fragment is directly tied to the activity that contains it.
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
View listView = getActivity().findViewById(R.id.list);
9. Creating event callback to the activity
In some cases, you might need a fragment to share events with the activity. A good way to do that is to define a callback interface inside the
fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the
information with other fragments in the layout as necessary.
For example, if a news application has two fragments in an activity—one to show a list of articles (fragment A) and another to display an
article (fragment B)—then fragment A must tell the activity when a list item is selected so that it can tell fragment B to display the article.
In this case, the OnArticleSelectedListener
interface is declared inside fragment A:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public static class FragmentA extends ListFragment { ... // Container Activity must implement this interface public interface OnArticleSelectedListener { public void onArticleSelected(Uri articleUri); } ... }
Then the activity that hosts the fragment implements the OnArticleSelectedListener
interface and overrides onArticleSelected()
to notify
fragment B of the event from fragment A.
To ensure that the host activity implements this interface, fragment A's onAttach()
callback method (which the system calls when adding the
fragment to the activity) instantiates an instance of OnArticleSelectedListener
by casting the Activity
that is passed into onAttach()
:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public static class FragmentA extends ListFragment { OnArticleSelectedListener mListener; ... @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mListener = (OnArticleSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); } } ... }
If the activity has not implemented the interface, then the fragment throws a ClassCastException
For example, if fragment A is an extension of ListFragment
, each time the user clicks a list item, the system calls onListItemClick()
in the
fragment, which then calls onArticleSelected()
to share the event with the activity
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
public static class FragmentA extends ListFragment { OnArticleSelectedListener mListener; ... @Override public void onListItemClick(ListView l, View v, int position, long id) { // Append the clicked item's row ID with the content provider Uri Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id); // Send the event and Uri to the host activity mListener.onArticleSelected(noteUri); } ... }
10. Fragment Lifecycle
11. Examples
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.
First, However, not all screen configurations are wide enough to show both the list of plays and the summary, side by side. So, the layout
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 at res/layout/fragment_layout.xml
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
//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 = "mirror.android.fragmentactivity.MainActivity$TitleFragment"//修改资源文件之后, 需要说明class = 包名 + 类名A$类名B android:id="@+id/titles" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
///land\fragment_layout <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 = "mirror.android.fragmentactivity.MainActivity$TitleFragment" 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" /> </LinearLayout>
fragment_layout.xml 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.
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).
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
//MainActivity package mirror.android.fragmentactivity; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.app.ListFragment; import android.content.Intent; import android.os.Bundle; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.ScrollView; import android.widget.Scroller; import android.widget.TextView; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_layout); } public static class TitleFragment extends ListFragment{ //To tell tablet and phones boolean mDualPane; int mCurCheckPosition; @Override public void onActivityCreated(Bundle savedInstanceState) { // TODO Auto-generated method stub 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) { // TODO Auto-generated method stub super.onSaveInstanceState(outState); outState.putInt("curChoice", mCurCheckPosition); } @Override public void onListItemClick(ListView l, View v, int position, long id) { // TODO Auto-generated method stub 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_fragment = (DetailsFragment)getFragmentManager().findFragmentById(R.id.details); if (details_fragment == null || details_fragment.getShownIndex() != index) { // Make new fragment to show this selection. details_fragment = 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_fragment); 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); } } } 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 bundle = new Bundle(); bundle.putInt("index", index); f.setArguments(bundle); return f; } public int getShownIndex() { return getArguments().getInt("index", 0); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // TODO Auto-generated method stub 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; } } }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
//DetailsActivty package mirror.android.fragmentactivity; import mirror.android.fragmentactivity.MainActivity.DetailsFragment; import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; public class DetailsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub 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(); } } }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
//Shakespeare.java package mirror.android.fragmentactivity; public interface Shakespeare { public static String[] DIALOGUE={"Detail of A","Detail of B","Detail of C","Detail of D"}; public static String[] TITLES={"A","B","C","D"}; }
Add DetailsActivity to the AndroirMainfest.xml
Run , ManiActivity OnCreateActivity ---> the initial state is different as below.