0% found this document useful (0 votes)
47 views

Building Dynamic Ui For Android Devices

The document discusses building dynamic user interfaces for Android devices. It covers using action bars, tabs and swipe views to display dynamic application data, using fragments for multi-pane layouts on different screen sizes, and using resources to support different resolutions and densities.

Uploaded by

Amparo Ortiz
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
47 views

Building Dynamic Ui For Android Devices

The document discusses building dynamic user interfaces for Android devices. It covers using action bars, tabs and swipe views to display dynamic application data, using fragments for multi-pane layouts on different screen sizes, and using resources to support different resolutions and densities.

Uploaded by

Amparo Ortiz
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

Building Dynamic UI for Android* Devices

Abstract
What does “Dynamic UI” mean for Android* developers? As an Android app developer, you want the UI
of your application to adapt to the dynamic nature of the application content. You also want the UI of
your application to fit and flow well on the majority of Android devices, regardless of the size of the
display, the screen resolution, or the pixel density. Given the variety of Android devices on the market
and the challenge of keeping up with the latest Android SDK, this can be a difficult task.

In this document, we will focus on a few Android UI programming techniques that will help you to
achieve the goal of a dynamic UI – use of the action bar, tab, and swipe views with dynamic application
data to enhance the screen navigation, use of Android fragments to design multiple-pane and master-
view layout for screens with different sizes of display, and use of the Android resource system to
improve the presentation of graphics and text content for screens with different resolution and density.

Table of Contents
1. Introduction
2. Sample restaurant menu application
3. Action bar, tab, swipe view, and dynamic application data
4. Android UI fragment, multi-pane layout, and master-detail view
5. Android resource system, graphics and text, screen resolution and density
6. Conclusion
7. References

Introduction
This document discusses dynamic UI building from 3 aspects:

1. Displaying dynamic content with the latest Android UI pattern and controls – At minimum, you
want the UI of your application to “live” and “breathe” like the rest of the Android systems. One
key is to design your app with the latest Android UI components, controls, and navigation
patterns. A certain degree of customization is not a problem, but in general, you want to adhere
to the latest Android guidelines and trends. And, how do we present the dynamic application
content with this latest UI trend? In this paper, we will demonstrate the use of the latest
Android UI components, such as the action bar, tab, and swipe view, to present dynamic
application data.
2. UI layout and navigation flow – Does it make sense to have the same UI layout for both 4”
phone and 11” tablet? To maximize the space from the large display, sometimes you want to
consider using different layouts for devices with different sizes of display. In this document, we
will discuss the use of Android Fragment to design multi-pane/single-pane layouts and master-
detail view to fit screens with different display sizes.
3. Display resolution and density – Besides the UI layout, if you use graphics in your app, what can
you do to ensure the graphics are not stretched or pixelated on devices with different screen
resolutions and pixel densities? What about the font size of a text item? A text item with a font
size of 20 may look perfect on a phone, but it could be too small on a tablet. We will discuss
what you can do with the Android resource system to handle these issues.

1
Sample Restaurant Menu App
To illustrate the programming concepts described in this document, I wrote an Android app that allows
users to browse a restaurant menu organized by food category. The restaurant menu application
provides programming examples of the topics discussed in this document.

Please note, the readers of this document are assumed to have basic knowledge of Java programming
and Android development concepts. It is not the intention of this document to provide Android tutorial,
rather, it focuses on the few essential UI techniques to help developers to achieve the goal of building a
dynamic UI.

Also, the code snippets in the document are sample code taken from the restaurant app. The snippets
are chosen to illustrate the programming concepts discussed in the document only. They do not provide
a complete view of the application structure and details. If you are interested in a comprehensive view
of Android application development provided by the sample application, such as life cycle handling of
fragments, UI update of grid item selection, retaining the user selection and application data from a
fragment during configuration change, or example of UI styling in the resource, please refer to the
Android developer links in the References section for details.

Action Bar, Tab, Swipe View, and Dynamic Application Data


While working on the UI for “Restaurant Menu” application, there are a few design considerations in
mind:

1. The UI shall allow users to access essential app features from main screen
2. The UI shall allow the owner of the restaurant menu application to add/remove food items
dynamically
3. The UI shall keep a consistent presentation and view switching between food categories
4. The UI shall present the food and information of the menu with images whenever possible
5. The UI shall allow for the use of gestures that are intuitive to the Android system for screen
navigation

The Android action bar, tab, and swipe views are chosen to fulfill the above requirements. In fact, since
Android 3.0, these UI elements are one of the most important UI patterns recommended by Android.
You will find the use of these UI elements in most of the stock Android applications, such as email,
calendar, hangouts, music, and play store. The following screen shots show how we present a browsable
restaurant menu with these UI elements.

2
Figure 1 Screen overview of a restaurant menu application

Creating an action bar and tabs with swipe views


This section describes how you can create an action bar and tabs with swipe views for an Android
application.

1. Add ViewPager to the layout file of the main screen to handle swipe views for tabs
(activity_main.xml)

<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<android.support.v4.view.PagerTitleStrip
android:id="@+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#333333"

3
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:textColor="#ffffff"/>
</android.support.v4.view.ViewPager>

2. In the MainActivity.java (the main screen of the application), inflate the activity_main.xml
layout, retrieve the ViewPager from the activity layout file, create a ViewPagerAdapter to handle
the creation and initialization of each page for the tab, and assign the OnPageChangeListener to
the ViewPager

// Setting up swipe view for each tab


mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setOnPageChangeListener(this);
mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), this);
mViewPager.setAdapter(mPagerAdapter);

3. Implement OnPageChangeListener and handle application related tasks during view change. At
minimum, the code should set the selected tab on the action bar when users swipe the view to
the next tab.

/**
* This method is called when the user swipes the tab from one to another
*/
public void onPageSelected(int position) {
// on changing the page
// make respected tab selected
mActionBar.setSelectedNavigationItem(position);
mCurrentViewedCategory = (String) mActionBar.getTabAt(position).getText();
}

/**
* Tab swipe view related callback
*/
public void onPageScrolled(int arg0, float arg1, int arg2) {
}

/**
* Tab swipe view related callback
*/
public void onPageScrollStateChanged(int arg0) {
}

4. Define PagerAdapter (inherits from FragmentStatePagerAdapter) to handle the view for each
tab. Pager Adapter is defined as an inner class to MainActivity.java in the sample. “getItem” is
called during the initialization of each page. In this case, each page contains a fragment of the
master-detail view of the menu data shown as figure 1. The details of fragment programming
are discussed in the next section.

4
Tips
There are two types of PagerAdapter – FragmentPagerAdapter and
FragmentStatePagerAdapter. For memory efficiency, the former is recommended if the
number of the pager is fixed, the latter is recommended if the number of the pager is
dynamically allocated. In the case of FragmentStatePagerAdapter, the pager is
destroyed when user navigates away from the page. The sample uses
FragmentStatePagerAdapter as the number of food category can change based on the
application data.

/**
* Fragment pager adapter to handle tab swipe view. Each tab view contains
* an ultimate fragment which includes a grid menu view and a detail view.
* Depending on the orientation of the device, the app decides whether to
* show both views or just grid view.
*/

class PagerAdapter extends FragmentStatePagerAdapter {


UltimateViewFragment ultimateViewFragment;
FragmentActivity mFragmentActivity;
UltimateViewFragment[] fragmentArray = new
UltimateViewFragment[mCategory.size()];

public PagerAdapter(FragmentManager fm, FragmentActivity fragmentActivity) {


super(fm);
mFragmentActivity = fragmentActivity;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
super.instantiateItem(container, position);
UltimateViewFragment fragment = (UltimateViewFragment)
super.instantiateItem(container, position);
fragment.setGridItemListener((GridItemListener)
mFragmentActivity);
fragmentArray[position] = fragment;
return fragment;
}

@Override
public Fragment getItem(int position) {
Bundle args = new Bundle();
// Each ultimate view is associated with one menu category
args.putString(MenuFactory.ARG_CATEGORY_NAME,
mCategory.get(position));
ultimateViewFragment = new UltimateViewFragment();
ultimateViewFragment.setArguments(args);
// Register as a GridItemListener to receive the notification of grid
// item click
ultimateViewFragment.setGridItemListener((GridItemListener)

5
mFragmentActivity);
fragmentArray[position] = ultimateViewFragment;
return ultimateViewFragment;
}

@Override
public int getCount() {
// Return number of tabs
return mCategory.size();
}

@Override
public CharSequence getPageTitle(int position) {
//Return the title of each tab
return mCategory.get(position);
}
}

5. Retrieve the ActionBar (mActionBar) from the Activity and set the navigation mode to be
NAVIGATION_MODE_TABS
6. Add tabs to the ActionBar with mActionBar.addTab and initialize the tab title with specified text

// Setting up action bar and tabs


mActionBar = getActionBar();
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for (int i = 0; i < mCategory.size(); i++) {
mActionBar.addTab(mActionBar.newTab().setText(
mCategory.get(i)).setTabListener(this));
// Initialize selected items in the hashtable with the first item
// of each category
if (savedInstanceState == null) {
mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuWithCategory(
mCategory.get(i)).get(0));
} else {
//update the mSelectedItems from the last saved instance
String[] selectedItems = savedInstanceState.getStringArray("selectedItems");
mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuItem(
mCategory.get(i),selectedItems[i]));
}
}

Tips
The ActionBar API was first introduced in Android 3.0 (API level 11), but it is also
available in support library for compatibility with Android 2.1 (API level 7) and above.
The sample code uses android-support-v4.jar library. The jar is placed in libs folder under
application root directory. If you use Eclipse as your IDE, please add the path to the
library in Project Properties->Java Build Path->Libraries

6
Adding action items to action bar
The action bar contains action items that are used frequently by the user. These action items are located
on the top of the action bar to allow easy access. In the sample code, we define a few action items, such
as camera, search, call, check-out, and settings, as highlighted in the screen shot below.

Figure 2 Action bar with action items

Here is the how you add action items to the action bar.

1. Define the action items in xml under res/menu (e.g. action_bar.xml)

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

<item android:id="@+id/action_camera"
android:icon="@drawable/ic_action_camera"
android:title="@string/action_camera"
android:showAsAction="always" />
<item android:id="@+id/action_search"
android:title="@string/action_search_str"
android:icon="@drawable/ic_action_search"
android:showAsAction="always"
android:actionViewClass="android.widget.SearchView" />
<item android:id="@+id/action_call"
android:icon="@drawable/ic_action_phone"
android:title="@string/action_call"
android:showAsAction="always" />
<item android:id="@+id/action_checkout"
android:icon="@drawable/ic_action_shoppingcart_checkout"
android:title="@string/action_checkout"
android:showAsAction="always"/>
<item android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>

</menu>

2. Inflate the action item menu in the code (MainActivity.java)

/**
* Initialize the action menu on action bar

7
*/
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.action_bar, menu);

//Set up the search feature


SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView =
(SearchView) menu.findItem(R.id.action_search).getActionView();
searchView.setSearchableInfo(
searchManager.getSearchableInfo(getComponentName()));
return super.onCreateOptionsMenu(menu);
}

3. Handle the action item click in the code

/**
* This method is called when the user click an action from the action bar
*/
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}

// Handle presses on the action bar items


switch (item.getItemId()) {
// Handle up/home navigation action
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
// Handle search
case R.id.action_search:
return true;
// Handle settings
case R.id.action_settings:
return true;
// Handle camera
case R.id.action_camera:
return true;
//Handle check out feature
case R.id.action_checkout:
return true;
default:
return super.onOptionsItemSelected(item);
}

8
Tips
As shown in Figure 1, the action bar contains action items which are frequently
performed by users, such as camera, search, and checkout. To keep a consistent look and
feel across the system, you can download the Action Bar Icon Pack from
https://developer.android.com/design/downloads/index.html and use them in the
application resource area.

Action bar styling


Android renders the action bar with a system-defined color which may not always match the color
theme of your application. Sometimes, you want to style the action bar with a style and color that is
designed for the theme of your application (or business theme color).

For example, the action bar in this sample app is styled with a “maroon” color that matches with the
application icon.

Figure 3 Action bar styling example

In this section, we will discuss how to enhance the look of the action bar using Android Action Bar Style
Generator.

1. The tool is available at http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html


2. Specify the color of your choice and download the generated resource files from the link. The
following is what I use in the sample app.

9
Figure 4 Action bar style generator

10
3. The tool generates sample resources icons, images, style xml as the followings. Add all resource
files to the application resource drawable area, and update AndroidManifest.xml
application:theme to use the style xml generated from the tool.

Figure 5 Action bar resources example

<activity
android:theme="@style/Theme.Example"
android:name="com.example.restaurant.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

Handling dynamic application data from the UI


The application UI may not always be static, especially when it has the need to present data that could
change during the lifetime of the application. For example, a picture album application allows users to
view and edit a number of images that could change any time while the device is on. An email
application needs to handle the messages which are refreshed every configurable time interval from the
server. In this sample restaurant menu application, the food menu is dynamic. The content of the action
bar tab and menu grid could change depending on the food category and menu.

One way to deal with dynamic data is to create a data factory, which acts as a translator between the
raw data and the UI. The data factory abstracts out the data manipulation logic from the UI which allows
the data to change without changing the UI logic. At a high level, the data factory communicates with
data sources for raw application data, processes the raw data from various sources (such as network,
local file system, database, or cache), and transforms the raw data into data objects which can be used
by the UI components.

11
The following shows a simple flow among UI, data factory, and data sources.

Figure 6 General flows to handle dynamic application content

Tips
Steps in handling dynamic application content
 Identify possible data sources, such as network, local file system, database, or
device cache
 Create a data factory that “listens” to the change in data source or query data
periodically depending on the needs of the application
 Data factory handles the request of the data update in a separate thread to
avoid blocking of the UI thread as data request and process could take time
 UI components registers as a data change listener to the Data Factory, and
refreshes UI components when a data change notification is received
 Data Factory manages the listeners of the data change event and provide
convenient methods for callers to query data without knowledge of raw data
format

12
To simplify the implementation in the sample restaurant app, food menu data are stored as a string
array in Android strings.xml. Each array element contains a record for the food item. In reality, these
data records could be defined and stored in a server to allow changes dynamically. Regardless of the
source the data, the data format defined in string.xml can be reused. The following shows how
application data is processed in MenuFactory.java and how the UI components are initialized with
application data.

1. Define menu data in strings.xml. Each food item contains a string with category name, menu
name, description, nutrition facts, price, and the name of the image. The data field is separated
by “,,,” delimiter.

<string-array name="menu_array">

<item>Appetizer,,,Angels on horseback,,,Oysters wrapped in bacon, served hot. In the United


Kingdom they can also be a savoury, the final course of a traditional British ,,,Calories 393; Fat 22
g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg; Carbohydrate 19g; Fiber 3g; Protein
31g,,,6.99,,,Angels on horseback.jpg</item>

<item>Appetizer,,,Batata vada,,,A popular Indian vegetarian fast food in Maharashtra, India. It


literally means potato fritters. The name \"Batata\" means potato in English. It consists of a
potato mash patty coated with chick pea flour, then deep-fried and served hot with savory
condiments called chutney. The vada is a sphere, around two or three inches in
diameter.,,,Calories 393; Fat 22 g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg;
Carbohydrate 19g; Fiber 3g; Protein 31g,,,7.99,,,Batata vada.jpg</item>

<item>Appetizer,,,Barbajuan,,,An appetizer mainly found in the eastern part of French Riviera


and Northern Italy.,,,Calories 393; Fat 22 g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg;
Carbohydrate 19g; Fiber 3g; Protein 31g,,,8.99,,,Barbajuan.jpg</item>

<item>Appetizer,,,Blooming onion,,,Typically consists of one large onion which is cut to


resemble a flower, battered and deep-fried. It is served as an appetizer at some
restaurants.,,,Calories 393; Fat 22 g( Saturated 4 g); Cholesterol 101 mg; Sodium 836 mg;
Carbohydrate 19g; Fiber 3g; Protein 31g,,,9.99,,,Blooming onion.jpg</item>

</string-array>

2. During application start-up, MainActivity.java creates a reference to MenuFactory as a singleton


and loads the data from the Android resource area.

mMenuFactory = MenuFactory.getInstance(res);
mMenuFactory.loadDataFromAndroidResource();

3. MenuFactory processes the data from strings.xml and transforms them to MenuItem objects
used by UI views.

/* Allows caller to load the app data from Android resource area */
public void loadDataFromAndroidResource() {
if (mMenuItems != null && mMenuItems.size() > 0) {
clear();
}
mMenuItems = new ArrayList<MenuItem>();

13
mCategoryList = new ArrayList<String>();

String[] menuList = mResources.getStringArray(R.array.menu_array);


MenuItem menuItem;
String[] currentMenu;
String currentCategory = "";

for (int i = 0; i<menuList.length; i++) {


currentMenu = menuList[i].split(",,,");
menuItem = new MenuItem();
for (int j = 0; j< currentMenu.length; j++) {
switch (j) {
case 0:
menuItem.setCategory(currentMenu[j]);
if (!currentMenu[j].equals(currentCategory)) {
currentCategory = currentMenu[j];
mCategoryList.add(currentMenu[j]);
}
break;
case 1:
menuItem.setName(currentMenu[j]);
break;
case 2:
menuItem.setDescription(currentMenu[j]);
break;
case 3:
menuItem.setNutrition(currentMenu[j]);
break;
case 4:
menuItem.setPrice(currentMenu[j]);
break;
case 5:
menuItem.setImageName(currentMenu[j]);
break;
}
}
menuItem.setId(Integer.toString(i));
mMenuItems.add(menuItem);
}
}

4. MenuFactory.java provides convenient methods for callers to query data related to the UI
updates.

/* Allows caller to retrieve the category list based on menu items */


public ArrayList<String> getCategoryList() {
return mCategoryList;
}

/* Allows caller to retrieve a list of menu based on passed in category */


public ArrayList<MenuItem> getMenuWithCategory (String category) {
ArrayList<MenuItem> result = new ArrayList<MenuItem>();

14
for (int i = 0; i<mMenuItems.size(); i++) {
MenuItem item = mMenuItems.get(i);
if (item.category.equals(category)) {
result.add(item);
}
}

return result;
}

/* Allows caller to retrieve menu item based on passed category and index */
public MenuItem getMenuItem (String category, int index) {
ArrayList<MenuItem> menuList = getMenuWithCategory(category);
if (menuList.size() == 0) {
return null;
}
return menuList.get(index);
}

/* Allows caller to retrieve menu item based on passed category and name */
public MenuItem getMenuItem (String category, String name) {
MenuItem result = null;
for (int i = 0; i<mMenuItems.size(); i++) {
MenuItem item = mMenuItems.get(i);
if (item.category.equals(category) && item.name.equals(name)) {
result = item;
}
}
return result;
}

/* Data structure for menu item */


class MenuItem {
String category;
String name;
String price;
String description;
String nutrition;
ImageView image;
String imageName;
String id;

public void setCategory(String str) {


category = str;
}

public void setName(String str) {


name = str;
}

public void setDescription(String str) {


description = str;

15
}

public void setNutrition(String str) {


nutrition = str;
}

public void setImageName(String str) {


imageName = str;
}

public void setId(String str) {


id = str;
}

public void setPrice(String str) {


price = str;
}

public void setImageView(ImageView imageView) {


image = imageView;
}
}

5. Initialize the grid menu view with food items from MenuFactory. The implementation is done in
MenuGridFragment.java. In the restaurant application, food items for each category are
displayed in a GridView which is embedded in a Fragment. During the fragment initialization,
MenuFactory is called to retrieve the food items for each category. The data adapter
(ImageAdapter) of the grid view creates and renders each food item during the initialization.

/* Inflating view item for the grid view and initialize the image adapter
* for the grid view.
*/
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

View rootView = inflater.inflate(


R.layout.fragment_menu_grid, container, false);

mMenuList = mMenuFactory.getMenuWithCategory(mCategory);
mGridView = (GridView) rootView.findViewById(R.id.gridview);
mGridView.setAdapter(mImageAdapter);
mGridView.setOnItemClickListener(this);
return rootView;
}

/* Image adapter for the grid view */


class ImageAdapter extends BaseAdapter {
private LayoutInflater mInflater;

16
public ImageAdapter(Context c) {
mInflater = LayoutInflater.from(c);
}

public int getCount() {


return mMenuList.size();
}

public Object getItem(int position) {


return mMenuList.get(position);
}

public long getItemId(int position) {


return position;
}

// create a new ImageView for each item referenced by the Adapter


public View getView(int position, View convertView, ViewGroup parent) {

View v = convertView;
ImageView picture;
TextView name;
TextView price;

if(v == null) {
v = mInflater.inflate(R.layout.view_grid_item, parent, false);
v.setTag(R.id.picture, v.findViewById(R.id.picture));
v.setTag(R.id.grid_name, v.findViewById(R.id.grid_name));
v.setTag(R.id.grid_price, v.findViewById(R.id.grid_price));
}

picture = (ImageView)v.getTag(R.id.picture);
name = (TextView)v.getTag(R.id.grid_name);
price = (TextView) v.getTag(R.id.grid_price);

MenuItem item = (MenuItem) mMenuList.get(position);

InputStream inputStream = null;


AssetManager assetManager = null;
try {
assetManager = getActivity().getAssets();
inputStream = assetManager.open(item.imageName);
picture.setImageBitmap(BitmapFactory.decodeStream(inputStream));
} catch (Exception e) {
} finally {
}

name.setText(item.name);
price.setText(item.price);

//Highlight the item if it's been selected


if (mSelectedPosition == position){

17
updateGridItemColor(v, true);
} else {
updateGridItemColor(v, false);
}

return v;
}
}

6. Initialize action bar tabs with food categories from MenuFactory

// Retrieve the category list from MenuFactory


mCategory = mMenuFactory.getCategoryList();
// Setting up action bar and tabs
mActionBar = getActionBar();
mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
for (int i = 0; i < mCategory.size(); i++) {
mActionBar.addTab(mActionBar.newTab().setText(
mCategory.get(i)).setTabListener(this));
// Initialize selected items in the hashtable with the first item
// of each category
if (savedInstanceState == null) {
mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuWithCategory(
mCategory.get(i)).get(0));
} else {
//update the mSelectedItems from the last saved instance
String[] selectedItems = savedInstanceState.getStringArray("selectedItems");
mSelectedItems.put(mCategory.get(i), mMenuFactory.getMenuItem(
mCategory.get(i),selectedItems[i]));
}
}

Android Fragment, Multi-Pane Layout, and Master-Detail View


Another aspect of a Dynamic UI is how you can design the UI of your Android application so that the
same application will fit and flow well on devices with difference sizes (such as tablets and phones). In
this section, we will discuss the use of an Android fragment to design multi-pane layout for devices with
different display sizes.

Android introduces the concept of “fragment” in 3.0. You can view “fragment” as a way to
“componentize” your screen layout. The screen is componentized into multiple UI groups or views. Each
UI group or view is implemented as a fragment. Your application decides which UI groups/views and
navigation flow is available to users during run time based on the display of the device it is running on.

Multi-pane layouts are a common usage of an Android fragment, in which the screen is presented as a
combination of multiple views. Interaction with one view on a screen could result in the update of
another view on the screen. Master-detail view is one of the important UI design patterns for this
concept. The application presents the overview of the content in a master view with a list or grid view
widget. Selecting an item on the grid or list shows the detail view of the item on the same screen or a

18
different screen. On a device with a large display (tablet), both master and detail view could fit on the
same screen. On a device with a smaller screen (phone), master and detail view could be presented on
different screens.

The restaurant menu app presents menu information for each food category in a grid view. Selecting the
grid item shows the detail of the food item on the same screen if the device has a large display, or a
different screen if the device has a smaller screen. This design is implemented in 3 Fragments:

1. UltimateVIewFragment – A fragment which contains a grid view fragment and a detail view
fragment, the visibility of inner fragments are determined during run time based on the size of
screen (e.g. detail view is only shown if the device has big display)
2. GridViewFragment – A fragment that presents the menu data for each food category in a grid
view
3. DetailViewFragment – A fragment that presents the detail of a food item selected in the grid
view

Tips
Most of the code samples on the Android developer site show the implementation of
master-detail view with two Fragments embedded inside an Activity, but not with two
fragments embedded inside a fragment. In the sample restaurant app, we implemented
the latter one. The use of a fragment, instead of an activity, is required by the swipe view
of action bar tabs. The sample restaurant code shows you in detail how this can be done
with tab swipe view.

Figure 7 Multi-pane, master-detail view for device with different screen sizes

19
Creations of fragments
This sections describes the steps required to create fragments used in the sample.

1. Define the screen layout with a fragment in xml. The following are screen layouts for
UltimateVIewFragment, GridViewFragment, and DetailViewFragment. Please note, the visibility
of the detail view below is set to “gone” initially, it should be changed in the respective layout
file for devices of different screen sizes. For example, if the device has a larger display, the
visibility is set to “visible”. See section “Multi-pane and single-pane layouts based on screen
size” later on for details.

This section shows screen layout for UltimateViewFragment only. Please see the complete code
sample for screen layout for GridViewFragment (layout/fragment_menu_grid.xml) and
DetailViewFragment (layout/fragment_disch_detail.xml).

<!—UltimateViewFragment screen layout 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">

<FrameLayout
android:id="@+id/grid_fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>

<FrameLayout
android:id="@+id/detail_fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
android:orientation="vertical"/>

</LinearLayout>

2. Create and initialize fragments programmatically and use FragmentManager to handle the
transactions of fragments. The following code snippet shows the implementation of
UltimateViewFragment which creates MenuGridFragment and DetailViewFragment during run
time. For details to implement MenuGridFragment and DetailViewFragment, please see the
complete sample code.

public void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);

20
if (savedInstanceState != null) {
String tmp = savedInstanceState.getString("selectedIndex");
if (tmp != null) {
mSelectedIndex = Integer.parseInt(tmp);
}
mCategory = savedInstanceState.getString("currentCategory", mCategory);
} else {
mCategory = (String) getArguments().getString(MenuFactory.ARG_CATEGORY_NAME);
}
mDetailViewFragment = new DetailViewFragment();
mGridViewFragment = new MenuGridFragment();

mGridViewFragment.setCategory(mCategory);
mGridViewFragment.setOnGridItemClickedListener(this);

FragmentManager fragmentManager = this.getChildFragmentManager();


FragmentTransaction transaction = fragmentManager.beginTransaction();

if (savedInstanceState != null) {
transaction.replace(R.id.detail_fragment, mDetailViewFragment);
transaction.replace(R.id.grid_fragment, mGridViewFragment);
} else {
transaction.add(R.id.detail_fragment, mDetailViewFragment, "detail_view");
transaction.add(R.id.grid_fragment, mGridViewFragment, "grid_view");
}
transaction.commit();
}

Tips
The fragments should be created during run time of an activity, especially if you plan to
swap fragments in and out of a screen dynamically. Fragments can be added, replaced,
and removed from the screen using FragmentManager. As shown in the code, the
FragmentManager can be retrieved from getChildFragmentManager, not
getFragmentManager, since the container of the children fragments is a fragment, not
an Activity. Also, during an orientation change, to avoid adding the same fragments on
top of each other in UltimateViewFragment, the code should replace the existing
fragments with the new fragment using “replace”, not “add” from FragmentTransaction.

Communication between fragments and activities


The communication between fragments can be handled by using the listener pattern, which involves
two easy steps:

1. Defining a listener interface that can be implemented by components which are interested in
receiving the notification from other components.

21
2. In the case of multi-pane and master-detail fragments, if a child fragment is sending the
notification, the container of the child fragments will register as a listener to the child fragment.
Upon receiving of the notification, the parent fragment or activity can take appropriate actions
based on the received information.

Here is how it is done in the restaurant app:

1. Define a GridItemListener interface. The interface is implemented by the container of the grid
fragment. The grid fragment notifies the parent container when a grid selection happens.

/**
* An interface implemented by classes which want to receive notification
* when a menu item is clicked on the grid. This interface is used by
* UltimateViewFragment, ActionBarActivity, DetailView to communicate the selected
* menu item.
*/
public interface GridItemListener {
public void onGridItemClick(com.example.restaurant.MenuFactory.MenuItem
itemSelected, int position);
}

2. UltimateViewFragment provides a method for caller to register itself as GridItemListener.

/* Allow caller to set the grid item listener */


public void setGridItemListener(GridItemListener gridItemListener) {
mGridItemListener = gridItemListener;
}

3. UltimateViewFragment notifies its listener of a change in grid selection.

/* Handle the event of item click from the menu grid */


public void onGridItemClick(MenuItem itemSelected, int position) {
mGridItemListener.onGridItemClick(itemSelected, position);
mSelectedIndex = position;
View detail = getActivity().findViewById(R.id.detail_fragment);
//portrait mode
if (detail != null && detail.getVisibility() == View.GONE) {
Intent intent = new Intent(this.getActivity(), DetailActivity.class);
intent.setAction("View");
intent.putExtra("category", itemSelected.category);
intent.putExtra("entree_name", itemSelected.name);
Activity activity = getActivity();
activity.startActivity(intent);

//landscape mode
} else {
mDetailViewFragment.update(itemSelected);
}
}

22
4. In the MainActivity, each tab view is the container parent of the UltimateViewFragment.
MainActivity registers itself as the GridItemListener to keep track of the last selected food item
for each category.

@Override
public Object instantiateItem(ViewGroup container, int position) {
super.instantiateItem(container, position);
UltimateViewFragment fragment = (UltimateViewFragment)
super.instantiateItem(container, position);
fragment.setGridItemListener((GridItemListener)
mFragmentActivity);
fragmentArray[position] = fragment;
return fragment;
}

5. MainActivity takes appropriate actions in the listener callback when a notification is received.

/**
* This method is called when a grid menu item is clicked
*/
public void onGridItemClick(com.example.restaurant.MenuFactory.MenuItem
itemSelected, int position) {
mSelectedItems.put(itemSelected.category, itemSelected);
}

Multi-pane and single-pane layouts based on screen size


As discussed above, an Android fragment allows you to define multi-pane layouts for screens. But, how
do we use screen size to determine multiple-pane/single-pane layouts, and how do we provide the
different screen flows based on the multiple-pane/single-pane design? The Android resource system
provides configuration qualifiers for the application to handle multiple screen layouts.

Different layout files are provided in the resource system based on screen sizes. During application start-
up, Android will apply the appropriate layout files based on the display size. Before Android 3.2, you can
define the layout for small, normal, large, or xlarge screens sizes. Layout files can be defined in
res/layout-small for devices with a screen size smaller than 426dpx320dp, or res/layout-xlarge for
screen sizes which are larger than 960dpx720dp. The following is the definition of these screen sizes.

 xlarge screens are at least 960dp x 720dp


 large screens are at least 640dp x 480dp
 normal screens are at least 470dp x 320dp
 small screens are at least 426dp x 320dp

Here is how each size is mapped to the actual device size in inches.

23
Figure 8 Mapping of screen size qualifier to actual display size in inches

Starting in Android 3.2, the above screen size qualifier has been replaced with the use of qualifier
sw<N>dp where N is the pixel definition for the screen width. For example, for “large” screen, the layout
files can be provided in layout-sw600dp directory.

We can further add “portrait” or “landscape” definition in the resource qualifier. For example, I may
want to have a special layout for screen width 600dp and portrait mode only. In this case, a layout-
sw600dp-port will be created to store the layout.

The following is the layout structure based on screen size and orientation of the device used in the
sample application. For a mid-size tablet, I want to have single-pane layout for portrait mode since the
UI may be squeezed if we use multi-pane layout on a mid-size tablet (7” or 8” tablet).

Figure 9 Multiple layout files in resource system for different display sizes

The fragment_ultimate_view.xml layout for different screen sizes is largely the same. This only thing
that is different is the visibility of the child fragment. For a mid-size tablet, the ultimate view layout will
look like this:

<!—UltimateViewFragment screen layout 


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"

24
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">

<FrameLayout
android:id="@+id/grid_fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>

<FrameLayout
android:id="@+id/detail_fragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:visibility="gone"
android:orientation="vertical"/>

</LinearLayout>

How do we handle the navigation flow for different layouts? During the application run time, based on
the visibility of the view, the application can decide whether to update other views on the same screen
(multi-pane) or to start a new screen (single-pane). The following code snippet shows how this is done:

/* Handle the event of item click from the menu grid */


public void onGridItemClick(MenuItem itemSelected, int position) {
mGridItemListener.onGridItemClick(itemSelected, position);
mSelectedIndex = position;
View detail = getActivity().findViewById(R.id.detail_fragment);
//portrait mode
if (detail != null && detail.getVisibility() == View.GONE) {
Intent intent = new Intent(this.getActivity(), DetailActivity.class);
intent.setAction("View");
intent.putExtra("category", itemSelected.category);
intent.putExtra("entree_name", itemSelected.name);
Activity activity = getActivity();
activity.startActivity(intent);

//landscape mode
} else {
mDetailViewFragment.update(itemSelected);
}
}

Fragment life cycle handling


Just like Android Activity, fragments implement the lifecycle callbacks and take proper actions during the
start, pause, resume, stop, and destroy states of an application. The lifecycle management for a
fragment is similar to that of an Activity.

25
Tips
Besides the regular Activity life cycle callbacks, such as onCreate, onStart, onResume,
onPause, onStop, and onDestroy, fragments have a few extra lifecycle callbacks:

onAttach() – Called when the fragment has been associated with the activity
onCreateView() – Called to create the view hierarchy associated with the fragment
onActivityCreated() – Called when the activity’s onCreate() has returned
onDestroyView() – Called when the view hierarchy associated with the fragment is being
removed
onDetach() – Called when the fragment is being disassociated from the activity

The following shows the effect of an activity life cycle on fragment life cycle callbacks.

26
Figure 10 Mapping of application lifecycle to fragment lifecycle callbacks

Similar to Activity, fragments may need to save and restore application states during device
configuration changes, such as a change in device orientation, or an unexpected Destroyed activity to a
Paused activity. Saving application states will be handled in the onSaveInstanceState () callback, which is
called before the Activity is destroyed, and restoring application state can be handled in either the
onCreate (), onCreateView (), or onActivityCreated () callback. The following code snippet shows how
the restaurant app saves the index of the selected grid item in onSaveInstanceState () and restores it in
onCreate ().

public void onCreate (Bundle savedInstanceState) {

27
super.onCreate(savedInstanceState);

if (savedInstanceState != null) {
String selectedIndex = savedInstanceState.getString("selectedIndex");
if(selectedIndex != null) {
mSelectedPosition = Integer.parseInt(selectedIndex);
}
}

mImageAdapter = new ImageAdapter(getActivity());


}

/* Saved the last selected Index before orientation change */


public void onSaveInstanceState (Bundle outState) {
//only save the selected position if user has clicked on the item
if (mSelectedPosition != -1) {
outState.putString("selectedIndex",
Integer.valueOf(mSelectedPosition).toString());
}
}

Tips
There may be times that you don’t want your fragments to be recreated during
configuration change (device orientation change), perhaps due to the complexity of the
application state data. Calling setRetainInstance (true) on the container class of
fragments can prevent fragment from being recreated during configuration change.

Android Resource System, graphics and text, screen resolution and


density
What about text and graphics presentation on screen? A font size chosen for a phone device may be too
small for a tablet with a large screen. A graphic icon may look just right on a tablet, but appear too big
on a phone with a smaller screen. And, what about preventing an image from being stretched on
devices with different screen resolutions?

In this section, we will discuss a few techniques to make sure your text and graphics resources look well
on screen with different resolutions and densities.

Images and screen pixel density


Screen pixel density plays an important role in how graphics is presented on the screen. A same image
will show up bigger on a screen with low pixel density, simply because the size of one pixel is larger on a
low density device. Vice versa, the same image will show up smaller on a screen with high pixel density.
When you design your UI, you want to preserve the same look of the graphics as much as possible
across devices with different screen pixel densities.

28
In most cases, this can be handled by using a pixel-independent unit, such as “dp” and “wrap_content”
in specifying the dimension and layout of the graphics icon in the resource system. With a pixel-
independent unit, Android will adjust the graphics based on the pixel density of the screen. The
following sample demonstrates the use of “dp” and “wrap_content” in showing the image of food items
in a detailed view.

<ImageView
android:id="@+id/dish_image"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/cart_area"
android:minWidth="600dp"
android:minHeight="400dp"
android:scaleType="centerCrop"/>

Sometimes this is not sufficient. For example, a small image could look pixelated on a screen due to the
image scaling. In this case, you should provide alternative images in the res/drawable area to avoid the
problem. Images with different sizes can be provided in res/drawable-<xxxxxx> folders, in which xxxxxx
is the generalized density category. The following chart provides a reference to interpret the generalized
density in actual screen density.

Figure 11 Different drawable areas with graphics resources for devices with different display sizes

Figure 12 Mapping of generalized screen density to actual dpi value

Size of text and screen sizes


To enhance the readability of the text, sometimes it is necessary to adjust the font size based on the size
of the display. For example, in the sample restaurant app, I use a smaller font size for the name and
price of the food on the grid menu for a device with less than 600 pixels in screen width (like a phone). I

29
created a text “style” with different font sizes for screens with larger and smaller displays. The style files
are stored in res/values-sw<N>dp with respect to the screen size.

Figure 13 Different style files for screen with different sizes

The following style file specifies the font size of the text used in the grid menu item.

<!—text style for screen with dp smaller than 600 


<style name="GridItemText">
<item name="android:textColor">@color/grid_item_unselected_text</item>
<item name="android:textStyle">italic</item>
<item name="android:textSize">14sp</item>
</style>

<!—text style for screen with dp larger than 600 


<style name="GridItemText">
<item name="android:textColor">@color/grid_item_unselected_text</item>
<item name="android:textStyle">italic</item>
<item name="android:textSize">20sp</item>
</style>

The layout file for the grid item reference to the text style as defined above (view_grid_item.xml).

<TextView
android:id="@+id/grid_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingTop="15dp"
android:paddingBottom="15dp"
android:layout_gravity="right"
style="@style/GridItemText"/>

Conclusion

30
Android UI is one of the most interesting areas in Android programming. There are many things to
consider when designing and programming Android UI. This document discusses four essential concepts
that help you to achieve the goal of building a Dynamic UI:

1. Use of the latest recommended UI elements, such as the action bar, tab, and swipe view for
screen navigation.
2. Programming practices to handle dynamic application data and how they are used with the
action bar, tab, and swipe views.
3. Using Android fragments to implement multi-pane and master-detail view layouts for devices
with different screen sizes
4. Using the Android resource system to improve the look of graphics and text on a screen with
different resolutions and pixel densities

As Android continues evolving. A good practice is to embrace the latest UI techniques and to keep your
knowledge up to date with the latest UI concepts. Having these techniques in mind will help you with
your dynamic UI design for the many Android devices to come.

References

1. Action bar creation: https://developer.android.com/training/basics/actionbar/index.html


2. Action bar styling: https://developer.android.com/training/basics/actionbar/styling.html
3. Multi-pane layouts: https://developer.android.com/design/patterns/multi-pane-layouts.html
4. Fragment programming guide:
https://developer.android.com/guide/components/fragments.html
5. Multiple screen design: https://developer.android.com/guide/practices/screens_support.html
6. Android SDK reference: https://developer.android.com/reference/packages.html

About the Author


Mei-Lin Hsieh is a software engineer with 15 years of experience in mobile development at Intel
and other companies. She is currently in the Software Solutions Group working on scale
enabling projects for Android applications with tablets and phones.

31
Notices
INFORMATION IN THIS DOCUMENT IS PROVIDED IN CONNECTION WITH INTEL PRODUCTS.
NO LICENSE, EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY INTELLECTUAL
PROPERTY RIGHTS IS GRANTED BY THIS DOCUMENT. EXCEPT AS PROVIDED IN INTEL'S
TERMS AND CONDITIONS OF SALE FOR SUCH PRODUCTS, INTEL ASSUMES NO LIABILITY
WHATSOEVER AND INTEL DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY, RELATING TO
SALE AND/OR USE OF INTEL PRODUCTS INCLUDING LIABILITY OR WARRANTIES RELATING
TO FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR INFRINGEMENT OF ANY
PATENT, COPYRIGHT OR OTHER INTELLECTUAL PROPERTY RIGHT.

UNLESS OTHERWISE AGREED IN WRITING BY INTEL, THE INTEL PRODUCTS ARE NOT
DESIGNED NOR INTENDED FOR ANY APPLICATION IN WHICH THE FAILURE OF THE INTEL
PRODUCT COULD CREATE A SITUATION WHERE PERSONAL INJURY OR DEATH MAY OCCUR.

Intel may make changes to specifications and product descriptions at any time, without notice.
Designers must not rely on the absence or characteristics of any features or instructions marked
"reserved" or "undefined." Intel reserves these for future definition and shall have no
responsibility whatsoever for conflicts or incompatibilities arising from future changes to them.
The information here is subject to change without notice. Do not finalize a design with this
information.

The products described in this document may contain design defects or errors known as errata
which may cause the product to deviate from published specifications. Current characterized
errata are available on request.

Contact your local Intel sales office or your distributor to obtain the latest specifications and
before placing your product order.

Copies of documents which have an order number and are referenced in this document, or
other Intel literature, may be obtained by calling 1-800-548-4725, or go to:
http://www.intel.com/design/literature.htm

Software and workloads used in performance tests may have been optimized for performance
only on Intel microprocessors. Performance tests, such as SYSmark* and
MobileMark*, are measured using specific computer systems, components, software,
operations, and functions. Any change to any of those factors may cause the results to vary.
You should consult other information and performance tests to assist you in fully evaluating
your contemplated purchases, including the performance of that product when combined with
other products.

**This sample source code is released under the Intel Sample Source Code License Agreement

32

You might also like