One way to think of a fragment is as a sub-activity. And in fact, the semantics of a fragment are a lot like an activity. A fragment can have a view hierarchy associated with it, and it has a life cycle much like an activity’s life cycle.
Fragments can even respond to the Back button like activities do. If you were thinking, “If only I could put multiple activities together on a tablet’s screen at the same time,” then you’re on the right track. But because it would be too messy to have more than one activity of an application active at the same time on a tablet screen, fragments were created to implement basically that thought. This means fragments are contained within an activity. Fragments can only exist within the context of an activity; you can’t use a fragment without an activity.
Fragments can coexist with other elements of an activity, which means you do not need to convert the entire user interface of your activity to use fragments. You can create an activity’s layout as before and only use a fragment for one piece of the user interface. Fragments are not like activities, however, when it comes to saving state and restoring it later. The fragments framework provides several features to make saving and restoring fragments much simpler than the work you need to do on activities


Example 1: Shakespere Instrucment
/manifests/AndroidManifest.xml
Config DetailActivity action and category. This allows you to start the details activity from code but will not show the details activity as an app in the App list.
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.trankyphat.app.fragmens_shakespeareinstrucment"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- Take a moment to view this application’s manifest file. In it you find the main activity with a category of LAUNCHER so that it will appear in the device’s list of apps. Then you have the separate DetailsActivity with a category of DEFAULT. This allows you to start the details activity from code but will not show the details activity as an app in the App list. --> <activity android:name=".DetailsActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> </manifest>
/res/layout/activity_main.xml
activity_main.xml for all view (in this context only portait)
<?xml version="1.0" encoding="utf-8"?> <!-- This file is res/layout/main.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- The other thing to keep in mind is that the <fragment> tag is just a placeholder in this layout. You should not put child tags under <fragment> in a layout XML file The fragment tag’s class attribute specifies your extended class for the titles of your application. That is, you must extend one of the Android Fragment classes to implement your logic, and the <fragment> tag must know the name of your extended class. --> <fragment class="com.trankyphat.app.fragmens_shakespeareinstrucment.TitlesFragment" android:id="@+id/titles" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
/res/layout-land/activity_main.xml
activity_main.xml for landscape view
<?xml version="1.0" encoding="utf-8"?> <!-- This file is res/layout-land/main.xml --> <!--landscape mode, orientation horizontal--> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- The other thing to keep in mind is that the <fragment> tag is just a placeholder in this layout. You should not put child tags under <fragment> in a layout XML file The fragment tag’s class attribute specifies your extended class for the titles of your application. That is, you must extend one of the Android Fragment classes to implement your logic, and the <fragment> tag must know the name of your extended class. --> <fragment class="com.trankyphat.app.fragmens_shakespeareinstrucment.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="2" android:layout_width="0px" android:layout_height="match_parent" /> </LinearLayout>
/res/layout/detail.xml
<?xml version="1.0" encoding="utf-8"?> <!-- This file is res/layout/details.xml --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ScrollView android:id="@+id/scroller" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/text1" android:layout_width="match_parent" android:layout_height="match_parent" /> </ScrollView> </LinearLayout>
MainActivity.java
package com.trankyphat.app.fragmens_shakespeareinstrucment; import android.app.FragmentTransaction; import android.content.Intent; import android.content.res.Configuration; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { public static final String TAG = "MyMessage"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public boolean isMultiPane() { return getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; } /** * 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. */ public void showDetails(int index) { Log.v(TAG, "in MainActivity showDetails(" + index + ")"); if (isMultiPane()) { // Check what fragment is shown, replace if needed. /*to find out if there’s a details fragment in the layout, you can ask the fragment manager using findFragmentById(). This will return null if the frame layout is empty or will give you the current details fragment*/ 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 inside the frame with the new one. Log.v(TAG, "about to run FragmentTransaction..."); /* FragmentManager * The FragmentManager is a component that takes care of the fragments belonging to an activity. This includes fragments on the back stack and fragments that may just be hanging around Obviously, you can’t use the getFragmentManager() method on a fragment that has not been attached to an activity yet. * */ /*To ensure that each fragment properly participates in the rollback, a FragmentTransaction is created and managed to perform that coordination*/ FragmentTransaction ft = getFragmentManager().beginTransaction(); //Custom animation //ft.setCustomAnimations(R.animator.fragment_open_enter, //R.animator.fragment_open_exit); ft.setCustomAnimations(R.animator.bounce_in_down, R.animator.slide_out_right); //which will cause the new fragment to fade in as the old fragment fades out. //ft.setCustomAnimations(R.animator.fade_in, // R.animator.fade_out); //ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.replace(R.id.details, details); /*pressing the Back button will pop the current activity off the stack and return the user to whatever was underneath. If you choose to take advantage of the back stack for fragments, you will want to uncomment*/ //ft.addToBackStack(TAG); 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(this, DetailsActivity.class); intent.putExtra("index", index); startActivity(intent); } } }
DetailsFragment.java
Fragment
package com.trankyphat.app.fragmens_shakespeareinstrucment; 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.widget.TextView; public class DetailsFragment extends Fragment { private int mIndex = 0; /* *You might wonder why you didn’t simply set the mIndex value in newInstance(). The reason is that Android will, behind the scenes, re-create your fragment using the default constructor. Then it sets the arguments bundle to what it was before. Android won’t use your newInstance() method, so the only reliable way to ensure that mIndex is set is to read the value from the arguments bundle and set it in onCreate(). The convenience method getShownIndex() retrieves the value of that index. Now the only method left to describe in the details fragment is onCreateView(). And this is very simple, too * */ public static DetailsFragment newInstance(int index) { Log.v(MainActivity.TAG, "in DetailsFragment newInstance(" + index + ")"); DetailsFragment df = new DetailsFragment(); // Supply index input as an argument. Bundle args = new Bundle(); args.putInt("index", index); df.setArguments(args); return df; } /* * This is why it is so important not to do anything fancy in the newInstance() method: when the fragment gets re-created, it won’t do it through newInstance(). * */ public static DetailsFragment newInstance(Bundle bundle) { int index = bundle.getInt("index", 0); return newInstance(index); } @Override public void onCreate(Bundle myBundle) { Log.v(MainActivity.TAG, "in DetailsFragment onCreate. Bundle contains:"); if(myBundle != null) { for(String key : myBundle.keySet()) { Log.v(MainActivity.TAG, " " + key); } } else { Log.v(MainActivity.TAG, " myBundle is null"); } super.onCreate(myBundle); mIndex = getArguments().getInt("index", 0); } public int getShownIndex() { return mIndex; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log.v(MainActivity.TAG, "in DetailsFragment onCreateView. container = " + container); // Don't tie this fragment to anything through the inflater. // Android takes care of attaching fragments for us. The // container is only passed in so you can know about the // container where this View hierarchy is going to go. View v = inflater.inflate(R.layout.details, container, false); TextView text1 = (TextView) v.findViewById(R.id.text1); text1.setText(Shakespeare.DIALOGUE[ mIndex ] ); return v; } }
DetailsActivity.java
Use only for Portait view
package com.trankyphat.app.fragmens_shakespeareinstrucment; import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; //Invoking a Separate Activity When Needed /*You may wonder why you would ever launch this activity if you’re in landscape mode, and the answer is, you wouldn’t. However, once this activity has been started in portrait mode, if the user rotates the device to landscape mode, this details activity will get restarted due to the configuration change. So now the activity is starting up, and it’s in landscape mode. At that moment, it makes sense to finish this activity and let the MainActivity take over and do all the work.*/ /* * Must be config in Manifests file * */ public class DetailsActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { Log.v(MainActivity.TAG, "in DetailsActivity onCreate"); super.onCreate(savedInstanceState); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { // If the screen is now in landscape mode, it means // that our MainActivity is being shown with both // the titles and the text, so this activity is // no longer needed. Bail out and let the MainActivity // do all the work. finish(); /*The other choice for the user is to rotate the device to get back to landscape mode. Then your details activity will call finish() and go away, revealing the also-rotated main activity underneath*/ return; } if(getIntent() != null) { // This is another way to instantiate a details // fragment. DetailsFragment details = DetailsFragment.newInstance(getIntent().getExtras()); /*Another interesting aspect about this details activity is that you never set the root content view using setContentView(). So how does the user interface get created? If you look carefully at the add() method call on the fragment transaction, you will see that the view container to which you add the fragment is specified as the resource android.R.id.content. This is the top-level view container for an activity */ /* * android.R.id.content. This is the top-level view container for an activity, and therefore when you attach your fragment view hierarchy to this container, your fragment view hierarchy becomes the only view hierarchy for the activity. You * */ /* * From the user’s point of view, they are now looking at just the details fragment view, which is the text from the Shakespearean play. If the user wants to select a different title, they press the Back button, which pops this activity to reveal your main activity (with the titles fragment only). The other choice for the user is to rotate the device to get back to landscape mode. Then your details activity will call finish() and go away, revealing the also-rotated main activity underneath. * */ getFragmentManager().beginTransaction() .add(android.R.id.content, details) .commit(); } } }
TilesFragment.java
List fragment
package com.trankyphat.app.fragmens_shakespeareinstrucment; import android.app.Activity; import android.app.ListFragment; import android.content.Context; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ListView; //Extend List to show list item public class TitlesFragment extends ListFragment { private MainActivity myActivity = null; int mCurCheckPosition = 0; @Override public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { Log.v(MainActivity.TAG, "in TitlesFragment onInflate. AttributeSet contains:"); for(int i=0; i<attrs.getAttributeCount(); i++) { Log.v(MainActivity.TAG, " " + attrs.getAttributeName(i) + " = " + attrs.getAttributeValue(i)); } super.onInflate(context, attrs, savedInstanceState); } @Override public void onAttach(Context context) { Log.v(MainActivity.TAG, "in TitlesFragment onAttach; activity is: " + myActivity); super.onAttach(context); //this fragment will attch to MainActivity this.myActivity = (MainActivity)context; } @Override public void onCreate(Bundle icicle) { Log.v(MainActivity.TAG, "in TitlesFragment onCreate. Bundle contains:"); if(icicle != null) { for(String key : icicle.keySet()) { Log.v(MainActivity.TAG, " " + key); } } else { Log.v(MainActivity.TAG, " myBundle is null"); } super.onCreate(icicle); if (icicle != null) { // Restore last state for checked position. mCurCheckPosition = icicle.getInt("curChoice", 0); } } @Override public View onCreateView(LayoutInflater myInflater, ViewGroup container, Bundle icicle) { Log.v(MainActivity.TAG, "in TitlesFragment onCreateView. container is " + container); return super.onCreateView(myInflater, container, icicle); } @Override public void onActivityCreated(Bundle icicle) { Log.v(MainActivity.TAG, "in TitlesFragment onActivityCreated. icicle contains:"); if(icicle != null) { for(String key : icicle.keySet()) { Log.v(MainActivity.TAG, " " + key); } } else { Log.v(MainActivity.TAG, " icicle is null"); } super.onActivityCreated(icicle); // Populate list with our static array of titles. /*Because ListFragment manages the ListView, do not attach the adapter to the ListView directly. You must use the ListFragment’s setListAdapter() method instead. The activity’s view hierarchy is now set up, so you’re safe going back into the activity to do the showDetails() call*/ setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, Shakespeare.TITLES)); //Get ListView from List Fragment ListView lv = getListView(); lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE); lv.setSelection(mCurCheckPosition); myActivity.showDetails(mCurCheckPosition); } @Override public void onStart() { Log.v(MainActivity.TAG, "in TitlesFragment onStart"); super.onStart(); } @Override public void onResume() { Log.v(MainActivity.TAG, "in TitlesFragment onResume"); super.onResume(); } @Override public void onPause() { Log.v(MainActivity.TAG, "in TitlesFragment onPause"); super.onPause(); } @Override public void onSaveInstanceState(Bundle icicle) { Log.v(MainActivity.TAG, "in TitlesFragment onSaveInstanceState"); super.onSaveInstanceState(icicle); icicle.putInt("curChoice", mCurCheckPosition); } @Override public void onListItemClick(ListView l, View v, int pos, long id) { Log.v(MainActivity.TAG, "in TitlesFragment onListItemClick. pos = " + pos); myActivity.showDetails(pos); mCurCheckPosition = pos; } @Override public void onStop() { Log.v(MainActivity.TAG, "in TitlesFragment onStop"); super.onStop(); } @Override public void onDestroyView() { Log.v(MainActivity.TAG, "in TitlesFragment onDestroyView"); super.onDestroyView(); } @Override public void onDestroy() { Log.v(MainActivity.TAG, "in TitlesFragment onDestroy"); super.onDestroy(); } @Override public void onDetach() { Log.v(MainActivity.TAG, "in TitlesFragment onDetach"); super.onDetach(); myActivity = null; } }
Shakespere.java
Static data for demo
package com.trankyphat.app.fragmens_shakespeareinstrucment; public class Shakespeare { public static String TITLES[] = { "Henry IV (1)", "Henry V", "Henry VIII", "Romeo and Juliet", "Hamlet", "The Merchant of Venice", "Othello" }; public static String DIALOGUE[] = { "Text 1", "Text 2", "Text 3", "Text 4", "Text 5", "Text 6", "Text 7" }; }
That although fragments can be instantiated with a static factory method such as newInstance(), you must always have a default constructor and a way to save initialization values into an initialization arguments bundle.
Using fragment transactions to change what’s displayed to a user, and animating those transitions using cool effects.
References
http://developer.android.com/guide/components/fragments.html: The Android Developer’s Guide page to fragments.
http://developer.android.com/design/patterns/multi-pane-layouts.html: Android design guidelines for multipane layouts.
http://developer.android.com/training/basics/fragments/index.html: Android training page for fragments.