Fragments Overview
Fragment
components
allow you to organize your application code so that it is
easier to
support
different sized devices.
Fragments
are components with their own lifecycle and their own user interface.
They can be defined via layout files or via coding.
Fragments
always
run in the context of an
Activity
. If an
Activity
is stopped its
Fragments
will also be stopped; if an
Activity
is destroyed its
Fragments
will also get destroyed.
If a
Fragment
component
is defined in an XML layout file, the
android:name
attribute points to the
Fragments
class.
The base class for
Fragments
is
android.app.Fragment
. For special purposes you can also use more special classes, like
ListFragment
or
DialogFragment
.
The onCreateView() method is called by Android once the
Fragment
should create its user interface. Here you can inflate an layout. The
onStart() method is called once the
Fragment
gets visible.
Fragments
can be dynamically added and removed from an
Activity
via
Fragment
transactions. This will add the action to the history stack of the
Activity
, i.e. this will allow to revert the
Fragment
changes in the
Activity
via the back button.
Fragments
make it easy to re-use components
in different layouts, e.g. you
can
build
single-pane layouts for handsets
(phones) and multi-pane
layouts
for
tablets.
This is not limited to tablets; for example you can use
Fragments
also to support different layout for landscape and portrait
orientation. But as tablets offer significantly more space you
typically include more views into the layout and
Fragments
makes that
easier.
The typical example is a list of items in an activity. On a tablet you see the details immediately on the same screen on the right hand side if you click on item. On a handset you jump to a new detail screen. The following discussion will assume that you have two
Fragments
(main and
detail) but you can also have more. We will also
have one
main
activity and one detailed activity. On a tablet the main
activity
contains both
Fragments
in its layout, on a handheld it only
contains the main fragment.
To check for an fragment you can use the FragmentManager.
DetailFragment fragment = (DetailFragment) getFragmentManager(). findFragmentById(R.id.detail_frag); if (fragment==null || ! fragment.isInLayout()) { // start new Activity } else { fragment.update(...); }
To create different layouts with
Fragments
you can:
-
Use one activity, which displays two
Fragments
for tablets and only one on handsets devices. In this case you would switch theFragments
in the activity whenever necessary. This requires that the fragment is not declared in the layout file as suchFragments
cannot be removed during runtime. It also requires an update of the action bar if the action bar status depends on the fragment.
-
Use separate activities to host each fragment on a handset.
For
example, when the tablet UI uses two
Fragments
in an activity, use the same activity for handsets, but supply an alternative layout that includes just one fragment. When you need to switchFragments
, start another activity that hosts the other fragment.
The second approach is the most flexible and in general preferable way of using
Fragments
. In this case the main activity
checks if the detail fragment is
available in the layout. If the
detailed fragment is there, the main
activity tells the fragment that
is should update itself. If the
detail
fragment is not available the
main activity starts the detailed
activity.
It is good practice that
Fragments
do not manipulate each other. For this purpose a
Fragment
typically implements an interface to get new data from its
host
Activity
.
The following tutorial demonstrates how to use
In portrait mode
In landscape mode
Fragments
. The entry
Activity
(called
MainActivity
of our application)
will use different layouts for portrait and for
landscape mode.
In portrait mode
MainActivity
will show one Fragment with a list of names. If the user touches an
item in the list, a second
Activity
called
DetailActivity
will start and show the selected text.
In landscape mode
MainActivity
will show two
Fragments
. The first is again the
Fragments
which shows the list of names. The second
Fragment
shows the text of the current selected item. This is similar to the
portrait mode, but the whole information will be shown on one screen.
Create a new project
de.vogella.android.fragments
with an
Activity
called
MainActivity
.
Create or change the following layout files in the
"res/layout/" folder.
First create the following file called "details.xml". This layout will be used by the
Change the existing
First create the following file called "details.xml". This layout will be used by the
DetailFragment
.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/detailsText" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_horizontal|center_vertical" android:layout_marginTop="20dip" android:text="Large Text" android:textAppearance="?android:attr/textAppearanceLarge" android:textSize="30dip" /> </LinearLayout>
Change the existing
main.xml
file. This layout
will be used by
MainActivity
in landscape mode and shows two
Fragments
.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <fragment android:id="@+id/listFragment" android:layout_width="150dip" android:layout_height="match_parent" android:layout_marginTop="?android:attr/actionBarSize" class="de.vogella.android.fragments.ListFragment" ></fragment> <fragment android:id="@+id/detailFragment" android:layout_width="match_parent" android:layout_height="match_parent" class="de.vogella.android.fragments.DetailFragment" > <!-- Preview: layout=@layout/details --> </fragment> </LinearLayout>
Create now the
Create the
Fragment
classes. Create the
ListFragment
class.
package de.vogella.android.fragments; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; public class ListFragment extends android.app.ListFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); String[] values = new String[] { "Android", "iPhone", "WindowsMobile", "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2" }; ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override public void onListItemClick(ListView l, View v, int position, long id) { String item = (String) getListAdapter().getItem(position); DetailFragment fragment = (DetailFragment) getFragmentManager() .findFragmentById(R.id.detailFragment); if (fragment != null && fragment.isInLayout()) { fragment.setText(item); } else { Intent intent = new Intent(getActivity().getApplicationContext(), DetailActivity.class); intent.putExtra("value", item); startActivity(intent); } } }
Create the
DetailFragment
class.
package de.vogella.android.fragments; 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 DetailFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("Test", "hello"); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.details, container, false); return view; } public void setText(String item) { TextView view = (TextView) getView().findViewById(R.id.detailsText); view.setText(item); } }
We want that Android uses a different main.xml file in portrait
model
then in landscape mode.
For this reason create the "res/layout-port" folder.
In portrait mode Android will check the "layout-port" folder for fitting layout files. Only if we would not have a
Therefore create the following
Also create the "details_activity_layout.xml" layout file. This layout will be used in the
For this reason create the "res/layout-port" folder.
In portrait mode Android will check the "layout-port" folder for fitting layout files. Only if we would not have a
main.xml
file in
"layout-port", Android
would
check the "layout" folder.
Therefore create the following
main.xml
layout file in
"res/layout-port".
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <fragment android:id="@+id/listFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="?android:attr/actionBarSize" class="de.vogella.android.fragments.ListFragment" /> </LinearLayout>
Also create the "details_activity_layout.xml" layout file. This layout will be used in the
DetailActivity
which is only used in portrait mode. Please note that we could have
create this file also in the "layout" folder, but as it is only used
in portrait mode it is best practise to place it into this folder.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <fragment android:id="@+id/detailFragment" android:layout_width="match_parent" android:layout_height="match_parent" class="de.vogella.android.fragments.DetailFragment" /> </LinearLayout>
Create a new
Activity
called
DetailActivity
with the following class.
package de.vogella.android.fragments; import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; import android.widget.TextView; public class DetailActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Need to check if Activity has been switched to landscape mode // If yes, finished and go back to the start Activity if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { finish(); return; } setContentView(R.layout.details_activity_layout); Bundle extras = getIntent().getExtras(); if (extras != null) { String s = extras.getString("value"); TextView view = (TextView) findViewById(R.id.detailsText); view.setText(s); } } }
MainActivity
will remain unmodified.
package de.vogella.android.fragments; import android.app.Activity; import android.os.Bundle; public class MainActivity extends Activity {/** Called when the activity is first created. */@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
Run your example. If you run the application in portrait mode you
should see only one
The
Fragment
. Use Ctrl+F11 to switch the orientation. In horizontal mode you
should
see two
Fragments
. If you select an item in portrait mode a new
Activity
should get started with the selected item. In horizontal mode your
second
Fragment
should display the select item.ActionBar navigation with Fragments
Fragments can also be used in combination with theActionBar
for navigation. For this your main
Activity
needs to implement a
TabListener
which is responsible for moving between the tabs.
The
ActionBar
allows to add tabs to it via the
newTab()
method. The following code shows such an
Activity
. It uses two
Fragments
, called
DetailFragment
and
ImageFragment
. At this point you should be able to create these two
Fragments
yourself.
package de.vogella.android.fragment; import android.app.ActionBar; import android.app.ActionBar.Tab; import android.app.ActionBar.TabListener; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; import android.os.Bundle; public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // setup action bar for tabs ActionBar actionBar = getActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); actionBar.setDisplayShowTitleEnabled(false); Tab tab = actionBar .newTab() .setText("First tab") .setTabListener(new MyTabListener<DetailFragment>(this, "artist", DetailFragment.class)); actionBar.addTab(tab); tab = actionBar .newTab() .setText("Second Tab") .setTabListener(new MyTabListener<ImageFragment>(this, "album", ImageFragment.class)); actionBar.addTab(tab); } public static class MyTabListener<T extends Fragment> implements TabListener { private Fragment mFragment; private final Activity mActivity; private final String mTag; private final Class<T> mClass;/** * Constructor used each time a new tab is created. * * @param activity * The host Activity, used to instantiate the fragment * @param tag * The identifier tag for the fragment * @param clz * The fragment's Class, used to instantiate the fragment */public MyTabListener(Activity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; } /* The following are each of the ActionBar.TabListener callbacks */ public void onTabSelected(Tab tab, FragmentTransaction ft) { // Check if the fragment is already initialized if (mFragment == null) { // If not, instantiate and add it to the activity mFragment = Fragment.instantiate(mActivity, mClass.getName()); ft.add(android.R.id.content, mFragment, mTag); } else { // If it exists, simply attach it in order to show it ft.setCustomAnimations(android.R.animator.fade_in, R.animator.animationtest); ft.attach(mFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { if (mFragment != null) { ft.setCustomAnimations(android.R.animator.fade_in, R.animator.test); ft.detach(mFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { } } }
(vogella.com)
cool