Better User Interfaces with the Android Action Bar
The action bar is an important design element, usually at the top of each screen in an app, that provides a consistent familiar look between Android apps. It is used to provide better user interaction and experience by supporting easy navigation through tabs and drop-down lists. It also provides a space for the app or activity’s identity, thus enabling the user to know their location in the app, and easy access to the actions that can be performed.
The action bar was introduced in Android 3.0, although support for older versions can be achieved by using the Android Support Library. Before its release, the Options Menu was usually used to provide the actions and functionality that are now put on the action bar. The action bar is included by default in all activities for apps with a minSdkVersion of 11. You can disable it and opt to only use the options menu, but for better user experiences it’s better to use the action bar as it is visible to the user, while the options menu needs the user to request it and the user might not be aware of its existence in the first place.
This tutorial explores setting up the action bar and discusses the different configurations that it offers.
Setting up the Action Bar
To start off, we are going to create a new project. We won’t be using the Android Support Library, so make sure to select a minimum SDK version of 11 or above. When you run your project, the action bar will be included at the top of your app’s screen. It is included in all activities that use or inherit from the Theme.Holo theme – which is the default when the minSdkVersion is set to 11 or greater. A typical action bar is shown in the following figure.
The action bar consists of:
App icon – This is used to identify your app with a logo or icon.
View control – This can also be used to identify the app or the specific activity the user is on by the title. If your app has different views, it can also be used to display these and allow for easy switching between views.
Action buttons – These are used to display the most important and/or often used actions. If there isn’t enough space to show all of the action buttons, those that don’t fit are automatically moved to the action overflow.
Action overflow – This is used for the lesser used actions.
Adding Actions to the Action Bar
To add actions to the action bar, create a XML file in the res/menu directory where you will define each action. It is possible to define the actions in Java code, but you will write less code if you use XML. The contents of res/menu/main_activity_bar.xml are shown below. In this example, we’re using the Action Bar Icon Pack for the action icons. Download it and copy the necessary icons to the res/drawable-xxxx directory for the different screen densities.
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/action_search"
android:icon="@drawable/ic_action_search"
android:title="@string/action_search"
android:showAsAction="ifRoom" />
<item android:id="@+id/action_record"
android:icon="@drawable/ic_action_video"
android:title="@string/action_record"
android:showAsAction="ifRoom" />
<item android:id="@+id/action_save"
android:icon="@drawable/ic_action_save"
android:title="@string/action_save"
android:showAsAction="ifRoom" />
<item android:id="@+id/action_label"
android:icon="@drawable/ic_action_new_label"
android:title="@string/action_label"
android:showAsAction="ifRoom" />
<item android:id="@+id/action_play"
android:icon="@drawable/ic_action_play"
android:title="@string/action_play"
android:showAsAction="ifRoom" />
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:showAsAction="never" />
</menu>
Next, add the string literals to res/values/strings.xml, as shown below.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ActionBar</string>
<string name="action_settings">Settings</string>
<string name="action_search">Search</string>
<string name="action_record">Record Video</string>
<string name="action_save">Save</string>
<string name="action_label">Add Label</string>
<string name="action_play">Play Video</string>
<string name="hello_world">Hello world!</string>
</resources>
The icon attribute takes a resource ID for an image which will appear on the action bar with or without the title. To display the action title add withText to showAsAction. For example you could use android:showAsAction="ifRoom|withText" to indicate that if there is room on the bar for the action button and text, they should both be shown. To force an action to always be displayed, use always on showAsAction. However, this is not advisable as it might cause undesirable layout effects on smaller screens. If you must, limit it to one or two items.
You should always define the title attribute, even if you don’t want to display both the icon and title, for the following reasons:
The title will be used in the overflow if there isn’t enough space on the action bar for the action item.
It might not be obvious to the user what the action item does just from its icon alone and so providing a title enables them to long press it to reveal a tool-tip that displays the title.
The title provides accessibility for sight-impaired users, as the screen reader can read the menu item’s title.
Next, we need to implement the onCreateOptionsMenu() callback method in our activity. This inflates the menu resource into the given Menu object for use in the action bar. The code for this function is shown below.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_activity_bar, menu);
return super.onCreateOptionsMenu(menu);
}
Run the project and you should see something similar to the following figure. Some action buttons appear on the action bar while the rest can be seen on the expanded action overflow. On changing to landscape view, the action bar automatically adapts to the new width and displays more actions according to the guidelines given in the XML file.
Splitting the Action Bar
Since the action items share the action bar real estate with the app icon and title, you might want to split the action bar so that the action items appear at the bottom of the screen. This will give them more space, and thus more items will be visible to the user. If there is enough space, for example on larger screens or in landscape mode, the action bar will not be split.
To split the action bar, add android:uiOptions="splitActionBarWhenNarrow" to each activity in your manifest file that you want to have a split action bar. This only supports API level 14 and above. To add support for lower levels use the following meta-data element.
<activity
android:name="com.example.actionbar.MainActivity"
android:label="@string/app_name"
android:uiOptions="splitActionBarWhenNarrow" >
<meta-data android:name="android.support.UI_OPTIONS"
android:value="splitActionBarWhenNarrow" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Hiding the Action Bar
You might not want to have the action bar visible at all times to the user. A common example to this is the Gallery app which hides the action bar when the user is looking at an image and shows the action bar when they touch the image. To toggle action bar visibility on touch, add the following to your activity file.
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
toggleActionBar();
}
return true;
}
private void toggleActionBar() {
ActionBar actionBar = getActionBar();
if(actionBar != null) {
if(actionBar.isShowing()) {
actionBar.hide();
}
else {
actionBar.show();
}
}
}
On running the application, you will be able to show/hide the action bar by tapping the screen. You will notice that the content on the screen changes position with each show/hide. This is because when you hide/show the action bar, the activity resizes, affecting the content’s size and position. To prevent this, you should instead overlay the action bar as described next.
Overlaying the Action Bar
Overlaying the action bar provides a better hide/show experience since the activity doesn’t resize on each hide/show, allowing your content to stay put. You can enable overlaying by setting android:windowActionBarOverlay to true in your theme file. You should be using the Theme.Holo theme (or one of its descendants). If your minSdkVersion was set to 11, this should be the case.
In res/values/styles.xml, add the following:
<resources>
<style name="AppBaseTheme" parent="android:Theme.Light">
</style>
<!-- Application theme. -->
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:windowActionBarOverlay">true</item>
</style>
</resources>
Run the application, and notice that the content on the screen doesn’t change position when the action bar is hidden and revealed.
Adding the Up Navigation
All screens in your app that are not the main entrance to your app (the “home” screen) should offer the user a way to navigate to the logical parent screen in the app’s hierarchy by pressing the Up button in the action bar. Starting in API level 14, you can declare the logical parent of each activity by specifying the android:parentActivityName attribute in the activity element in the manifest file. To support lower versions, include the Support Library and specify the parent activity as the value for android.support.PARENT_ACTIVITY, matching the android:parentActivityName attribute.
We’ll add another activity to demonstrate this. Add another activity file called SecondActivity, as shown in the following code listing. We call setDisplayHomeAsUpEnabled() to allow Up navigation with the app icon in the action bar. This will add a left-facing caret alongside the app icon. When it is pressed, the activity receives a call to onOptionsItemSelected().
package com.example.actionbar;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
public class SecondActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_activity);
getActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.second_activity_bar, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}
In res/layout/second_activity.xml, add the following:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/TextView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="@string/hello_world_again" />
</RelativeLayout>
Next, add the following strings to res/values.strings.xml.
<string name="hello_world_again">Hello world, again!</string>
<string name="second">Go To Second Activity</string>
<string name="second_activity_title">Second Activity</string>
Then, create a resource file for the second activity’s action bar. Name this file res/menu/second_activity_bar.xml, and add the following XML.
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
</menu>
Next, add the activity to the manifest file:
<application>
...
<activity
android:name="com.example.actionbar.SecondActivity"
android:label="@string/second_activity_title"
android:parentActivityName="com.example.actionbar.MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.actionbar.MainActivity" />
</activity>
...
</application>
Then, add a button to the main activity in res/layout/activity_main.xml:
<Button
android:id="@+id/second"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/TextView1"
android:layout_centerVertical="true"
android:onClick="openSecondActivity"
android:text="@string/second" />
Also, add the onClick handler in MainActivity.java. This function, shown below, will start the second activity when the button is clicked.
public void openSecondActivity(View view) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
Run the application. There will be a button on the first screen that, when pressed, brings up a second screen with the action bar shown below. Notice the caret that navigates to the parent activity when clicked.
Action Bar Interactivity
So far we have created action items that do nothing when clicked on. Next we’ll see how to add some interactivity to the action bar.
Handling Clicks on Action Items
When an action is clicked, the activity’s onOptionsItemSelected() method is called. The action can be identified by calling getItemId(). Add the onOptionsItemSelected() method to MainActivity.java, as shown in the following example. Here we are displaying a different message when each action is clicked on.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_search:
// Code you want run when activity is clicked
Toast.makeText(this, "Search clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_record:
Toast.makeText(this, "Record clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_save:
Toast.makeText(this, "Save clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_label:
Toast.makeText(this, "Label clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_play:
Toast.makeText(this, "Play clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_settings:
Toast.makeText(this, "Settings clicked", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
Action Views
Action views are interactive widgets that appear within the action bar as a substitute for action bu r as a substitute for action buttons. They allow occasionally used UI items to be placed on the action bar, thus avoiding unnecessary consumption of screen space and switching of activities to use them.
To declare an action view, use either the actionLayout or actionViewClass attribute to specify either a layout resource or widget class to use, respectively. As a quick example, to add a SearchView widget to our example, modify the search action in res/menu/main_activity_bar.xml as follows.
<item android:id="@+id/action_search"
android:icon="@drawable/ic_action_search"
android:title="@string/action_search"
android:showAsAction="ifRoom|collapseActionView"
android:actionViewClass="android.widget.SearchView" />
On running the application, an edit text view will appear when the search action is clicked on.
We will add our own custom view to the action bar. Create a layout file named res/layout/my_action.xml. This will be the custom view’s layout resource.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<TextView
android:id="@+id/myActionTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Type here:"
android:gravity="right" />
<EditText
android:id="@+id/myActionEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="left" />
</LinearLayout>
Then, add the action item to res/menu/main_activity_bar.xml, as shown below.
<item android:id="@+id/my_action"
android:icon="@drawable/ic_action_edit"
android:title="My Action"
android:showAsAction="ifRoom|collapseActionView"
android:actionLayout="@layout/my_action" />
This will use the layout we added above. collapseActionView collapses the action view so that only the icon shows on the action bar and when clicked on, it expands to reveal an edit text view. Clicking on the Up caret exits from the action view.
Next, updated the main activity file, MainActivity.java, as shown below. Take note of the comments throughout the code.
package com.example.actionbar;
import android.os.Bundle;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Intent;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.MenuItemCompat.OnActionExpandListener;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements TextView.OnEditorActionListener {
private MenuItem myActionMenuItem;
private EditText myActionEditText;
public class MainActivity extends Activity implements TextView.OnEditorActionListener {
private MenuItem myActionMenuItem;
private EditText myActionEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_activity_bar, menu);
// Here we get the action view we defined
myActionMenuItem = menu.findItem(R.id.my_action);
View actionView = myActionMenuItem.getActionView();
// We then get the edit text view that is part of the action view
if(actionView != null) {
myActionEditText = (EditText) actionView.findViewById(R.id.myActionEditText);
if(myActionEditText != null) {
// We set a listener that will be called when the return/enter key is pressed
myActionEditText.setOnEditorActionListener(this);
}
}
// For support of API level 14 and below, we use MenuItemCompat
MenuItemCompat.setOnActionExpandListener(myActionMenuItem, new OnActionExpandListener() {
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// Do something when collapsed
return true; // Return true to collapse action view
}
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
// Do something when expanded
if(myActionEditText != null) {
myActionEditText.setText("");
}
return true; // Return true to expand action view
}
});
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
toggleActionBar();
}
return true;
}
private void toggleActionBar() {
ActionBar actionBar = getActionBar();
if(actionBar != null) {
if(actionBar.isShowing()) {
actionBar.hide();
}
else {
actionBar.show();
}
}
}
public void openSecondActivity(View view) {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_search:
// Code you want run when activity is clicked
Toast.makeText(this, "Search clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_record:
Toast.makeText(this, "Record clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_save:
Toast.makeText(this, "Save clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_label:
Toast.makeText(this, "Label clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_play:
Toast.makeText(this, "Play clicked", Toast.LENGTH_SHORT).show();
return true;
case R.id.action_settings:
Toast.makeText(this, "Settings clicked", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
if(keyEvent != null) {
// When the return key is pressed, we get the text the user entered, display it and collapse the view
if(keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
CharSequence textInput = textView.getText();
// Do something useful with the text
Toast.makeText(this, textInput, Toast.LENGTH_SHORT).show();
MenuItemCompat.collapseActionView(myActionMenuItem);
}
}
return false;
}
}
In this code, we get the edit text view in our action vie w layout myActionEditText and set a listener on it. We want an operation to be made when the user types in some text and presses return. By default, when the user presses Return, the cursor goes to a new line. We want to instead collapse the action view and get the user’s input. This is done by the onEditorAction method which checks on the type of key event on the edit text view. If it is Enter, the text input is captured and displayed. Then, the action view is collapsed.
We set a listener on the action view with MenuItemCompat.setOnActionExpandListener to know when the view expands and collapses. MenuItemCompat is used to support older API versions.
By default, when the user enters text and leaves the action view, on expanding it again, the previous text is still visible. We use onMenuItemActionExpand() to remove any text before the action view expands so that the edit text view will be blank.
Conclusion
The action bar is an important design element and it can be used to greatly improve an app’s user experience. We have looked at some configurations you can use on the action bar for your app’s UI, but there are more out there. For more information, check out the documentation. You can see the code used in this article on GitHub.
Comments
Post a Comment