Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Developing Mobile Applications
Week 4
Alternative layouts, fragments
Steve Jones
Department of Computer Science
University of Waikato
stevej@waikato.ac.nz
Alternative layouts, Fragments
1
Today
Providing alternative layouts for different
orientations
• portrait + landscape
Reusing components across activities and layouts Fragments
Alternative layouts, Fragments
2
Alternative layouts
Consider a standard list + detail UI design
when the handset is in landscape orientation
a single activity can show both list and detail
side by side
in portrait we need an activity to show the list
and another to show the detail, which we’ve
just seen
Alternative layouts, Fragments
3
Alternative layouts
We require different layouts depending upon
orientation
Defaults are provided in eg
• res/layout/activity_property_finder.xml
We can add other layout folders eg for when we are
in landscape orientation
• res/layout-land/activity_property_finder.xml
• these override the defaults (which are now effectively for
portrait)
Alternative layouts, Fragments
4
Alternative layouts
The default is what we already have in
• res/layout/activity_property_finder.xml
and we know it works for portrait mode
Add the layout-land folder and create a new layout
file
• res/layout-land/activity_property_finder.xml
Alternative layouts, Fragments
5
Alternative layouts
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ui_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ListView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/property_list"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="10dp"
android:layout_weight="3" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:orientation="vertical" >
<TextView
android:id="@+id/property_details_title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/property_details_price"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
• res/layout-land/activity_property_finder.xml
left to right
Alternative layouts, Fragments
takes up 3/7 of available width
takes up 4/7 of available width
same as our earlier details layout
6
Alternative layouts
Now the system will load/apply the appropriate layout
at run-time
This includes when orientation changes dynamically
• if onCreate() is being called again because the activity is
being recreated, so is
setContentView(R.layout.activity_property_finder)
• now 1 of 2 possible layout defn files are applied
depending upon current orientation
What’s the problem……..?
Alternative layouts, Fragments
7
Alternative layouts
What’s the problem……..?
• onItemClick needs to do different things dependent
upon orientation (update details view or start details
activity)
• we get completely new ListView and TextView objects
whenever the layout is created eg on rotation
• we have do some work to retain state even though it’s
really the same list and text views in both activities and
orientations
Enter fragments….
Alternative layouts, Fragments
8
Fragments
The list and detail elements
• will have the same implementation irrespective of
orientation
• we want to reuse them across activities in the
application ie we want exactly the same list and exactly
the same details, not copies or new ones configured to
look like the original
Alternative layouts, Fragments
9
Fragments
Fragments are a middle ground between activities and UI
components
They normally have a UI (but don’t have to) and some
behaviour/logic
Fragments can be hosted in one or more activities
So we might have
• one list fragment
• one detail fragment
and use them across our two-panel and single-panel
activities
Alternative layouts, Fragments
10
Fragments : multiple activity layouts
We can refactor the design
the PropertyFinderActivity
fills the screen
the left panel is the
fragment containing a list
Alternative layouts, Fragments
the right panel is the
fragment displaying the
details
11
Fragments : multiple activity layouts
So we can refactor the design
the PropertyFinderActivity
fills the screen
Alternative layouts, Fragments
the panel is the fragment
containing a list
the PropertyDetailsActivity
fills the screen
the panel is the fragment
displaying the details
12
Fragments : multiple activity layouts
So we can refactor the design
the same list fragment
Alternative layouts, Fragments
the same details fragment
13
Fragments
We define a fragment as a Java class, just like an
activity eg
PropertyDetailsFragment.java
public class PropertyDetailsFragment extends Fragment {
fragment classes must have a blank constructor
public PropertyDetailsFragment() {
call to super() is not needed
}
@Override
protected void onCreate(Bundle savedInstanceState) {
}
}
Alternative layouts, Fragments
fragments have lifecycle methods
similar but not identical to Activity lifecycle methods
fragment lifecycle methods also have to integrate with the
containing activity’s lifecycle
14
Fragments : adding via XML
Two ways to add fragments to an activity
1. via XML layout file eg our landscape layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ui_container"
android:layout_width="match_parent"
a wrapper to hold the list fragment
android:layout_height="match_parent"
not strictly necessary, but allows us to : (i) add other
android:orientation="horizontal" >
views to this screen section later if needed; (ii) keep
<FrameLayout
orientation dependent layout attributes separate from the
android:id="@+id/property_list_frame"
fragment
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="10dp"
android:layout_weight="3" >
declare a fragment element, identify exactly what type of
fragment by giving the full package and class name
<fragment
android:id="@+id/property_list_fragment"
android:name="org.stevej.android.propertyfinder5.PropertyListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="PropertyListFragment" />
tag is a string we can use to refer to the fragment in code
</FrameLayout>
Alternative layouts, Fragments
15
Fragments : adding via XML
Two ways to add fragments to an activity
1. via XML layout file eg our landscape layout
<FrameLayout
android:id="@+id/property_details_frame"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="10dp"
android:layout_weight="4" >
same pattern for the details fragment
<fragment
android:id="@+id/property_details_fragment"
android:name="org.stevej.android.propertyfinder5.PropertyDetailsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="PropertyDetailsFragment" />
</FrameLayout>
</LinearLayout>
Alternative layouts, Fragments
16
Fragments : adding via XML
Two ways to add fragments to an activity
1. via
XML using
layout XML
file egwe
ourare
landscape
layout
Note:
creating
static fragments
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ui_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<fragment android:name=”org.stevej.android.propertyfinder5.ProprtyListFragment"
android:id="@+id/property_list"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginRight="10dp"
android:layout_weight="3" />
<fragment android:name=”org.stevej.android.propertyfinder5.ProprtyDetailsFragment”
android:id="@+id/property_details”
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:orientation="vertical” />
</LinearLayout>
This means that we can’t remove
them at run-time. If we need to do
that we create dynamic fragments in
Java code
Alternative layouts, Fragments
17
Fragments : adding via Java
Two ways to add fragments to an activity
2. via Java code
• /res/layout-land/activity_property_finder.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ui_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<FrameLayout
android:id="@+id/property_list_frame"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="3" android:layout_marginRight="10dp" />
<FrameLayout
android:id="@+id/property_details_frame"
android:layout_width="0dp"
android:layout_height="fill_parent"
android:layout_weight="4" />
</LinearLayout>
Alternative layouts, Fragments
we’ll add the list fragment to
this frame in Java
we’ll add the details
fragment to this frame in
Java
18
Fragments : adding via Java
Two ways to add fragments to an activity
2. via Java code
• /res/layout-land/activity_property_finder.xml
provides ability to manipulate the current set
of fragments (add, remove, replace etc)
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.property_list_frame, new PropertyListFragment(), "PropertyListFragment");
ft.commit();
layout element that the fragment
will be inserted into
Alternative layouts, Fragments
fragment instance
fragment tag, used to retrieve
fragment later
19
Fragments
We will use the XML static fragment approach here
Now we have 2 activities
•
•
PropertyFinderActivity
PropertyDetailsActivity
2 fragments
•
•
PropertyListFragment
PropertyDetailsFragment
4 states and transitions between them
•
•
•
•
PropertyFinderActivity landscape
PropertyFinderActivity portrait
PropertyDetailsActivity landscape
PropertyDetailsActivity portrait
Our layout files handle inclusion of required fragments for both orientations
Alternative layouts, Fragments
20
PropertyFinder behaviour
But
• a click on a list item needs to either update the details
fragment (landscape) or launch the details activity
(portrait)
• we want to highlight the selected list item
• we need to ensure list and detail fragments show
correct items on orientation change, activity restart etc
(keep state)
• when the user has the details view in portrait and
rotates the device, we want to show them the 2 panel
view, not the details view in landscape
Alternative layouts, Fragments
21
App logic/flow
PropertyFinderActivity
PropertyListFragment
onListItemClick
call property
selected method
title, price
if portrait
start details activity
else
call details fragment update
method
Intent with title,
price data
title, price
PropertyDetailsActivity
data extracted from Intent
call details fragment
update method
Alternative layouts, Fragments
PropertyDetailsFragment
title, price
update UI
22
Fragments : initialisation
PropertyDetailsFragment
Empty constructor
No onCreate() method – we don’t need it because
there is no data initialisation etc to do
public class PropertyDetailsFragment extends Fragment {
Unlike activities there is no setContentView() method.
onCreateView automatically called – we inflate the
public PropertyDetailsFragment() {
fragment’s layout file into a View object and return it as
}
this fragment’s UI
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View details_view = inflater.inflate(R.layout.property_details, container, false);
return details_view;
}
our method to change what’s shown in the UI
getView() returns the top level view element in the
fragment’s UI
public void setContent(String title, String price) {
TextView title_view = (TextView) getView().findViewById(R.id.property_details_title);
TextView price_view = (TextView) getView().findViewById(R.id.property_details_price);
title_view.setText(title);
price_view.setText(price);
}
}
Alternative layouts, Fragments
23
Fragments : fragment manager
PropertyDetailsActivity
If the user is in this activity in portrait, then they
rotate the device to landscape – the system
recreates this activity
But we don’t need it (we have the details fragment
in landscape) so finish this activity immediately
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
finish();
return;
}
if we are in portrait this activity was
setContentView(R.layout.activity_property_details);
started from a list click with data
setUpActionBar();
provided with the intent (slides 31-32)
Intent intent = getIntent();
String title = intent.getStringExtra("property_title");
String price = intent.getStringExtra("property_price");
the activity UI elements are provided by the
fragment
get hold of the fragment and call its method to
display the data
PropertyDetailsFragment property_details_fragment = (PropertyDetailsFragment) getFragmentManager().findFragmentByTag("PropertyDetailsFragment");
property_details_fragment.setContent(title, price);
}
Alternative layouts, Fragments
24
Fragments : ListFragment
PropertyListFragment is more complex… needs to
• retain state (data, current selection)
• communicate with the details fragment to show data for
selected item
• provide visual selection feedback
• ensure correct list item is selected whenever list
becomes visible (including orientation change)
Alternative layouts, Fragments
25
Fragments : retaining state
PropertyListFragment initialisation
• very similar to our list activity
a list in a fragment is really common –
there’s a built in class for it
provides a listview etc
public class PropertyListFragment extends ListFragment {
private ArrayList<Property> properties = new ArrayList<Property>();
private PropertyListAdapter property_list_adapter;
public PropertyListFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
properties.add(new Property("Exclusive Townhouse", "$450,000", R.drawable.ic_launcher));
properties.add(new Property("Close to Uni", "$375,000", R.drawable.ic_launcher));
properties.add(new Property("A Large Family Home", "$400,000", R.drawable.ic_launcher));
property_list_adapter = new PropertyListAdapter(getActivity(), R.layout.property_list_item, R.id.property_title, properties);
setRetainInstance(true);
the key to retaining the state of this fragment
}
Alternative layouts, Fragments
basically it makes the FragmentManager keep hold of this specific
PropertyListFragment instance when it would otherwise be destroyed
26
(eg containing activity is destroyed)
Fragments : retaining state
PropertyListFragment initialisation
• setRetainInstance(true)
The system’s default behaviour is to destroy/recreate
fragments along with their parent activity ie we get brand
new instances of the fragment
• onCreate() and onDestroy() methods of the fragment can be
called multiple times (eg orientation changes)
Setting this flag stops this behaviour
Instance variables retain their values
Alternative layouts, Fragments
27
Fragments : communication
Handling item clicks for both portrait and landscape
Keep logic about whether to launch details activity or
modify details fragment out of the list fragment
• it would need to work out the context in which it is being
used – simple here, difficult in more complex UIs
Common pattern is to handle this in parent activity
via a listener interface
• PropertySelectionListener
Alternative layouts, Fragments
28
Fragments : communication
PropertySelectionListener
package org.stevej.android.propertyfinder5;
public interface PropertySelectionListener {
public void onPropertySelected(Property property);
}
our main activity provides an implementation
public class PropertyFinderActivity extends Activity implements PropertySelectionListener {
@Override
if we don’t have the details frame
public void onPropertySelected(Property property) {
(fragment not present)
if (findViewById(R.id.property_details_frame) == null) {
Intent intent = new Intent(this, PropertyDetailsActivity.class);
intent.putExtra("property_title", property.getTitle());
intent.putExtra("property_price", property.getPrice());
startActivity(intent);
} else {
PropertyDetailsFragment property_details_fragment = (PropertyDetailsFragment) getFragmentManager().findFragmentByTag("PropertyDetailsFragment");
property_details_fragment.setContent(property.getTitle(), property.getPrice());
}
}
Alternative layouts, Fragments
29
Fragments : communication
Why not check orientation == portrait?
public void onPropertySelected(Property property) {
if (findViewById(R.id.property_details_frame) == null) {
} else {
}
}
Because if we had some other layout design (eg for
10in tablet) we may have the details fragment in either
orientation eg could have
• landscape: list + detail + images
• portrait: list + detail
Alternative layouts, Fragments
30
Fragments : communication
The list fragment can use that listener
public class PropertyListFragment extends ListFragment {
private ArrayList<Property> properties = new ArrayList<Property>();
private PropertyListAdapter property_list_adapter;
private PropertySelectionListener property_selection_listener;
public PropertyListFragment() {}
fragment lifecycle method, called
automatically when the fragment is
@Override
attached to its containing activity
public void onAttach(Activity activity) {
super.onAttach(activity);
property_selection_listener = (PropertySelectionListener) activity;
}
the activity implements the selection
listener interface
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
Alternative layouts, Fragments
31
Fragments : ListFragment
Handle click in list fragment
public class PropertyListFragment extends ListFragment {
private ArrayList<Property> properties = new ArrayList<Property>();
private PropertyListAdapter property_list_adapter;
private PropertySelectionListener property_selection_listener;
private int selected_item_position = 0;
public PropertyListFragment() {}
update record of currently selected item
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
property_selection_listener = (PropertySelectionListener) activity;
}
highlight the item
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
selected_item_position = position;
getListView().setItemChecked(selected_item_position, true);
property_selection_listener.onPropertySelected(property_list_adapter.getItem(position));
}
Alternative layouts, Fragments
call the listener method, providing the selected property
32
Fragments : ListFragment
Reselect the item after eg orientation change
fragment lifecycle method –
automatically called once the fragment’s
parent activity has been created
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
show the list content rather than a
‘loading’ icon
setListShown(true);
getListView().setAdapter(property_list_adapter);
only one selection at a time
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
highlight selected item
getListView().setItemChecked(selected_item_position, true);
make sure selected item is in view
getListView().smoothScrollToPosition(selected_item_position);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
property_selection_listener.onPropertySelected(property_list_adapter.getItem(selected_item_position));
}
}
if we are in landscape we can update the details fragment
Alternative layouts, Fragments
33
Fragments : ListFragment highlighting
Managing highlighting in the list
• by default clicked items flash, but the highlighting isn’t
persistent
• we need to define a style that is used when a list item is
activated (selected/clicked)
property_list_item.xml
<RelativeLayout xmlns:android=http://schemas.android.com/apk/res/android
style="@style/activated”
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dip" >
Alternative layouts, Fragments
34
Fragments : ListFragment highlighting
We need to define a style that is used when a list
item is activated (selected/clicked)
values-v11 because were are
res/values-v11/styles.xml
targeting Android 3.0 (which
introduced the Holo theme) and up
applied when the item is
activated
we want a style value from
<resources>
this theme
<style name="activated" parent="android:Theme.Holo.Light">
<item name="android:background">?android:attr/activatedBackgroundIndicator</item>
</style>
</resources>
we’ll modify the background
of the list item
Alternative layouts, Fragments
syntax for looking up a built
in attribute value
value we want
35
PropertyFinder
Now we have multi-activity, multi-fragment
application that correctly handles state changes
Next… loading real data
• making network requests
• handling long running tasks (like network requests)
Alternative layouts, Fragments
36