2. FRAGMENTS
A fragment is an independent
component which can be used by an activity. A fragment encapsulates
functionality so that it is easier to reuse within activities and layouts.
A fragment runs in the context of an activity,
but has its own life cycle and typically its own user interface. It is also
possible to define fragments without an user interface, i.e., headless
fragments.
Fragments can be dynamically or statically added
to an activity.
Fragments make it easy to reuse 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 on a smartphone.
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 smartphone you jump to a new detail
screen. This is depicted in the following graphic.
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.
The following screenshots demonstrates this
usage.
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 the Fragments in the
activity whenever necessary. This requires that the fragment is not declared in
the layout file as such Fragments 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 switch Fragments,
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 it should
update itself. If the detail fragment is not available, the main activity starts
the detailed activity.
To define a new fragment you extend either the android.app.Fragment class
or one of its subclasses, for example ListFragment, DialogFragment, PreferenceFragment or WebViewFragment.
The following code shows an example implementation.
package com.example.android.rssfeed;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class DetailFragment extends Fragment {
@Override
public View
onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view =
inflater.inflate(R.layout.fragment_rssitem_detail,
container, false);
return view;
}
public void setText(String item) {
TextView view = (TextView)
getView().findViewById(R.id.detailsText);
view.setText(item);
}
}
To use your new fragment you can statically add
it to an XML layout
To check if the fragment is
already part of your layout you can use the FragmentManager class.
DetailFragment fragment
= (DetailFragment) getFragmentManager().
findFragmentById(R.id.detail_frag);
if (fragment==null || ! fragment.isInLayout()) {
// start new Activity
}
else {
fragment.update(...);
}
If a fragment is defined in an XML layout file,
the android:name attribute points to the corresponding class.
A fragment has its own life cycle. But it is
always connected to the life cycle of the activity which uses the fragment.
The onCreate() method is called after
the onCreate() method of the activity but before the onCreateView()method
of the fragment.
The onCreateView() method is called by
Android once the fragment should create its user interface. Here you can
inflate a layout via the inflate() method call of the Inflator object
passed as a parameter to this method. There is no need to implement this method
for headless fragments.
The onActivityCreated() is called
after the onCreateView() method when the host activity is
created. Here you can instantiate objects which require a Context object.
Fragments don't subclass the Context you have to use the getActivity() method
to get the parent activity.
The onStart() method is called once
the fragment gets visible.
If an activity stops, its fragments are
also stopped; if an activity is destroyed its fragments are
also destroyed.
To increase reuse of Fragments they
should not directly communicate with each other. Every communication of the Fragments should
be done via the host activity.
For this purpose a Fragment should
define an interface as an inner type and require that the activity which
uses it, must implement this interface. This way you avoid that the Fragment has
any knowledge about the activity which uses it. In its onAttach() method
it can check if the activity correctly implements this
interface.
For example, assume you have a Fragment which
should communicate a value to its parent activity. This can be
implemented like the following.
package com.example.android.rssfeed;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
public class MyListFragment extends Fragment {
private
OnItemSelectedListener listener;
@Override
public View
onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view =
inflater.inflate(R.layout.fragment_rsslist_overview,
container, false);
Button button = (Button)
view.findViewById(R.id.button1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
updateDetail();
}
});
return view;
}
public interface OnItemSelectedListener {
public void onRssItemSelected(String link);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof OnItemSelectedListener) {
listener = (OnItemSelectedListener)
activity;
} else {
throw new ClassCastException(activity.toString()
+ " must implemenet MyListFragment.OnItemSelectedListener");
}
}
@Override
public void onDetach() {
super.onDetach();
listener = null;
}
// May also be triggered from the Activity
public void updateDetail() {
// create a string, just for testing
String newTime =
String.valueOf(System.currentTimeMillis());
// Inform the Activity about the change based
// interface defintion
listener.onRssItemSelected(newTime);
}
}
In fragments you also need to
store your application data. For this you can persists the data in a central
place. For example
·
SQlite database
·
File
·
The application object,
if this case the application need to handle the storage
If you want to persists data between
configuration changes you can also use the application object.
In addition to that you can use the setRetainState(true) method
call on the fragments. This retains the instance of the fragments between
configuration changes but only works if the fragments is not
added to the backstack. Using this method is not recommend by Google for fragments which
have an user interface. In this case the data must be stored as member (field).
If the data which should be stored is supported
by the Bundle class, you can use the onSaveInstanceState()method
to place the data in the Bundle, and retrieve that data the onActivityCreated() method.
The Fragment Manager class and the Fragment Transaction class
allow you to add, remove and replace fragments in the layout of your activity.
Fragments can be dynamically modified via transactions. To dynamically
add Fragments to an existing layout you typically define a
container in the XML layout file in which you add a Fragment. For
this you can use for example a FrameLayout element.
FragmentTransaction ft =
getFragmentManager().beginTransaction();
ft.replace(R.id.your_placehodler,
new YourFragment());
ft.commit();
A new Fragment will replace an
existing Fragment that was previously added to the container.
If you want to add the transaction to the
backstack of Android you use the addToBackStack() method. This will
add the action to the history stack of the activity, i.e. this will
allow to revert the Fragment changes via the back button.
During a Fragment transaction you can define
animations which should be used based on the Property Animation API via the setCustomAnimations() method.
You can also use several standard animations
provided by Android via the setTransition() method call. These are
defined via the constants starting with FragmentTransaction.TRANSIT_FRAGMENT_*.
Both methods allow you to define an entry
animation and an exist animation.
You can add a FragmentTransition to
the backstack to allow the user to use the back button to reverse the
transition.
For this you can use the addToBackStack() method
on the FragmentTransition object.
Fragments can be used without defining a user interface.
To implement a headless fragment simply
return null in the onCreateView() method of your fragment.
Headless fragment are typically used to encapsulate some
state across configuration changes or for a background processing task. For
this purpose you would set your headless fragment to be
retained. A retained fragment is not destroyed during
configuration changes.
To set your fragment to
retained, call its setRetainInstance() method.
To add such a Fragment to an activity you
use the add() method of the FragmentManager class. If you
need to refer to this Fragment later, you need to add it with
a tag to be able to search for it via the findFragmentByTag()method of the FragmentManager.
Fragments can also contribute entries to the ActionBar.
To do this, call setHasOptionsMenu() in theonCreate() method of
the fragment. The Android framework calls in this case the onCreateOptionsMenu()method
in the Fragment class and adds its menu items to the ones
added by the activity.