User interface toolkits as diverse as Java Swing, the Apple Macintosh toolbox, Microsoft Windows, and browser JavaScript all feature the ubiquitous “pop-up menu,” usually in the window-frame version and the context (in-window) form. Android follows this convention, with some variations to be expected due to the smaller screens used on many devices (e.g., pop-up or context menus cover a large portion of the screen).
Those other window systems also feature the ubiquitous “dialog,” a window smaller than the main screen that pops up to notify you of some condition or occurrence and asks you to confirm your acceptance, or asks you to make one of several choices, provide some information, and so on.
Android provides a fairly standard dialog mechanism, as well as a smaller, lighter “pop-up” called a toast. This only appears on the screen for a few seconds, and fades away on its own. Intended for passive notification of low-importance events, it is often used for error notification, although I advise against this usage.
There is also a Snackbar
, which looks like an action bar at the bottom of the screen, but behaves like a toast—it pops up, and then disappears after a few seconds.
And it doesn’t stop there. Android also provides a notification mechanism, which allows you to put text and/or an icon in the notifications bar (top left of the screen). A notification can optionally be accompanied by any combination of LED flashing, audio sounds, and device vibration.
Each of these interactive mechanisms is discussed in this chapter. The chapter proceeds in the same order as this introduction, from menus, to dialogs and toasts, to notifications.
Ian Darwin
Use a toast for a short notification that appears in front of your application, or a Snackbar for a short notification that occupies the bottom part of your application’s screen.
The toast mechanism is so basic that it is used everywhere. So named because its “pop up” action reminded an early developer of how an electric toaster pops up the bread when it’s toasted, the toast was designed to be easy to use:
Toast
.
makeText
(
context
,
message
,
length
).
show
();
The Context
argument can be an Activity or a Service.
The message
argument is a String
(or a CharSequence
), or the R.id
of a String
resource.
The length
argument is an integer, with one of the values Toast.LENGTH_SHORT
or
Toast.LENGTH_LONG
.
In older code you will often see the use of the nonfluent style:
Toast
myTemporaryToastVariable
=
Toast
.
makeText
(
context
,
message
,
length
);
myTemporaryToastVariable
.
show
();
When I see code like this, I tend to ask: “But why create a temporary variable that’s only ever used once? Do you get paid by the keystroke?” If you like this style, use it, but most developers will use the shorter style.
For a slightly neater effect, you may want to use a Snackbar. While the normal action bar appears at the top of an Activity, the Snackbar appears at the bottom. Like a toast, a Snackbar is normally used to indicate something that you want the user to see, but that’s not critical for them to they see—if it’s critical, and you want confirmation that they’ve seen it, you need them to make a choice, etc., then use a dialog instead (Recipe 7.6).
The Snackbar is used in a similar fashion to the toast, but the first argument is a View
:
Snackbar.make(view, message, length).show();
It is common to set an Action
with the Snackbar. This is not done using the normal setOnClickListener()
but instead using setAction()
, whose second argument is the familiar OnClickListener
(here implemented as a lambda; see Recipe 1.18):
Snackbar
.
make
(
view
,
pickRandomMessage
(),
Snackbar
.
LENGTH_LONG
)
.
setAction
(
"Tap Me!"
,
e
->
Log
.
d
(
TAG
,
"We should do something here"
))
.
show
();
The message
argument, a String
or CharSequence
, is displayed at the end of the Snackbar, and if the user taps on it, the OnClickListener
will be invoked. If the user does nothing, the entire Snackbar will disappear in a few seconds (based on the length
argument).
Figure 7-1 shows our example Snackbar in action.
Rachee Singh
Define an XML layout for the toast and then inflate the view in Java.
First, we will define the layout of the custom toast in an XML file, toast_layout.xml. It contains an ImageView
and a TextView
, as shown in Example 7-1.
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id=
"@+id/toast_layout_root"
android:orientation=
"horizontal"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:padding=
"10dp"
android:background=
"#f0ffef"
>
<ImageView
android:id=
"@+id/image"
android:layout_width=
"wrap_content"
android:layout_height=
"fill_parent"
android:layout_marginRight=
"10dp"
/>
<TextView
android:id=
"@+id/text"
android:layout_width=
"wrap_content"
android:layout_height=
"fill_parent"
android:textColor=
"#000000"
/>
</LinearLayout>
Then, in the Java code, we inflate this view using LayoutInflater
. We set the gravity and duration of the toast. The setGravity()
method modifies the position at which the toast will be displayed. On the click of the customToast
button, we show the toast (see Example 7-2).
customToast
=
(
Button
)
findViewById
(
R
.
id
.
customToast
);
LayoutInflater
inflater
=
getLayoutInflater
();
View
layout
=
inflater
.
inflate
(
R
.
layout
.
toast_layout
,
(
ViewGroup
)
findViewById
(
R
.
id
.
toast_layout_root
));
ImageView
image
=
(
ImageView
)
layout
.
findViewById
(
R
.
id
.
image
);
image
.
setImageResource
(
R
.
drawable
.
icon
);
TextView
text
=
(
TextView
)
layout
.
findViewById
(
R
.
id
.
text
);
text
.
setText
(
"Hello! This is a custom toast!"
);
final
Toast
toast
=
new
Toast
(
getApplicationContext
());
toast
.
setGravity
(
Gravity
.
CENTER_VERTICAL
,
0
,
0
);
toast
.
setDuration
(
Toast
.
LENGTH_LONG
);
toast
.
setView
(
layout
);
customToast
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
v
)
{
toast
.
show
();
}
});
The source code for this example is in the Android Cookbook repository, in the subdirectory CustomToast (see “Getting and Using the Code Examples”).
Rachee Singh
You want to show a menu when the user presses the Menu button on an Android device.
On ancient versions of Android such as Gingerbread, most devices had a physical Menu button,
whereas modern applications generally use an ActionBar
(see Recipe 6.5), which exposes
a “soft” Menu button consisting of three dots in a vertical stack.
First, create a directory named menu in the res directory of the project. In the menu directory, create a Menu.xml file. Example 7-3 shows the code for Menu.xml.
<menu
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<item
android:id=
"@+id/icon1"
android:title=
"One"
android:icon=
"@drawable/first"
/>
<item
android:id=
"@+id/icon2"
android:title=
"Two"
android:icon=
"@drawable/second"
/>
<item
android:id=
"@+id/icon3"
android:title=
"Three"
android:icon=
"@drawable/three"
/>
<item
android:id=
"@+id/icon4"
android:title=
"Four"
android:icon=
"@drawable/four"
/>
</menu>
In this XML code, we add a menu and to it we add as many items as our application requires. We can also provide an image for each menu item (in this example, default images have been used).
Now, in the Java code for the Activity, override the onCreateOptionsMenu()
:
@Override
public
boolean
onCreateOptionsMenu
(
Menu
menu
)
{
MenuInflater
inflater
=
getMenuInflater
();
inflater
.
inflate
(
R
.
menu
.
menu
,
menu
);
return
true
;
}
Figure 7-2 shows how the menu should look.
Rachee Singh
In the Java Activity, we need to override onOptionsItemSelected()
. This method takes in a MenuItem
and checks for its ID. Based on the ID of the item that is clicked, a switch
-case
can be used. Depending on the case
selected, an appropriate action can be taken. The custom menu might look something like Figure 7-2 from the previous recipe.
For this example, the code just displays one of several toasts indicating which menu item was selected. Here’s the source code:
@Override
public
boolean
onOptionsItemSelected
(
MenuItem
item
)
{
switch
(
item
.
getItemId
())
{
case
R
.
id
.
icon1
:
Toast
.
makeText
(
this
,
"Icon 1 Beep Bop!"
,
Toast
.
LENGTH_LONG
).
show
();
break
;
case
R
.
id
.
icon2
:
Toast
.
makeText
(
this
,
"Icon 2 Beep Bop!"
,
Toast
.
LENGTH_LONG
).
show
();
break
;
case
R
.
id
.
icon3
:
Toast
.
makeText
(
this
,
"Icon 3 Beep Bop!"
,
Toast
.
LENGTH_LONG
).
show
();
break
;
case
R
.
id
.
icon4
:
Toast
.
makeText
(
this
,
"Icon 4 Beep Bop!"
,
Toast
.
LENGTH_LONG
).
show
();
break
;
}
return
true
;
}
Figure 7-3 shows the result.
The source code for this project is in the Android Cookbook repository, in the subdirectory MenuAction (see “Getting and Using the Code Examples”).
Rachee Singh
Use a submenu implementation to provide additional options to the user.
A submenu is a part of a menu that displays options in a hierarchical manner. On desktop operating systems, submenus appear to “cascade” down and to the side (usually to the right side). Android devices may not have room for that, so submenus appear like dialogs in that they float over the main screen of the application, rather like a spinner (see Recipe 6.14). You can create the menu hierarchy in the following ways:
By inflating an XML layout
By creating the menu items in the Java code
While the first approach is by far the most common,
in this recipe we will follow the second approach to show that it’s possible,
creating the menu/submenu items in the onCreateOptionsMenu()
method.
First we add the submenu to the menu using the addSubMenu()
method. In order to prevent conflicts with other items in the menu, we explicitly provide the group ID and item ID to the submenu we are creating (specifying constants for the item ID and group ID). Then we set an icon for the header of the submenu with the seHeadertIcon()
method and an icon for the submenu with setIcon()
(see Example 7-4).
To add items to the submenu, we use the add()
method. As arguments to the method, the group ID, item ID, position of the item in the submenu, and text associated with each item are specified:
private
static
final
int
OPTION_1
=
0
;
private
static
final
int
OPTION_2
=
1
;
private
int
GROUP_ID
=
4
;
private
int
ITEM_ID
=
3
;
@Override public boolean onCreateOptionsMenu(Menu menu) { MenuItem mi = menu.add("Main Menu, Option 1"); mi.setShowAsAction(SHOW_AS_ACTION_IF_ROOM); SubMenu sub1 = menu.addSubMenu(GROUP_ID, ITEM_ID, Menu.NONE, R.string.submenu); sub1.setHeaderIcon(R.drawable.icon); sub1.setIcon(R.drawable.icon); sub1.add(GROUP_ID, OPTION_1, 0, "Submenu Option 1"); sub1.add(GROUP_ID, OPTION_2, 1, "Submenu Option 2"); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case OPTION_1: Toast.makeText(this, "Submenu 1, Option 1", Toast.LENGTH_LONG).show(); break; case OPTION_2: Toast.makeText(this, "Submenu 1, Option 2", Toast.LENGTH_LONG).show(); break; } return true; }
The onOptionItemSelected()
method is called when an item on the menu/submenu is selected. In this method, using a switch
-case
we check for the item that is clicked and an appropriate message is displayed.
Figure 7-4 shows the application before and after you press the Menu button, then the message that appears when you click a submenu item (#2 in this screen capture).
The source code for this example is in the Android Cookbook repository, in the subdirectory CustomSubMenu (see “Getting and Using the Code Examples”).
Rachee Singh
Through the AlertDialog
class, you can provide the user with up to three options that can be used in any scenario:
Positive reaction
Neutral reaction
Negative reaction
If the user has entered some data in an EditText
and is then attempting to cancel that Activity, the application should prompt the user to either save his changes, discard them, or cancel the alert dialog, which should also cancel the cancellation of the Activity.
Here is the code that would implement this kind of AlertDialog
, along with appropriate click listeners on each button on the dialog:
alertDialog
=
new
AlertDialog
.
Builder
(
this
)
.
setTitle
(
R
.
string
.
unsaved
)
.
setMessage
(
R
.
string
.
unsaved_changes_message
)
.
setPositiveButton
(
R
.
string
.
save_changes
,
new
AlertDialog
.
OnClickListener
()
{
public
void
onClick
(
DialogInterface
dialog
,
int
which
)
{
saveInformation
();
}
})
.
setNeutralButton
(
R
.
string
.
discard_changes
,
new
AlertDialog
.
OnClickListener
()
{
public
void
onClick
(
DialogInterface
dialog
,
int
which
)
{
finish
();
}
})
.
setNegativeButton
(
android
.
R
.
string
.
cancel_dialog
,
new
AlertDialog
.
OnClickListener
()
{
public
void
onClick
(
DialogInterface
dialog
,
int
which
)
{
Dialog
.
cancel
();
}
})
.
create
();
alertDialog
.
show
();
Pratik Rupwal
The code in Example 7-5 shows how to reveal the current time on the screen and shows a button that, when clicked, produces the Timepicker
widget through which the user can accept the time.
public
class
Main
extends
Activity
{
private
TextView
mTimeDisplay
;
private
Button
mPickTime
;
private
int
mHour
;
private
int
mMinute
;
static
final
int
TIME_DIALOG_ID
=
0
;
/** Called when the Activity is first created. */
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
main
);
// Capture our View elements
mTimeDisplay
=
(
TextView
)
findViewById
(
R
.
id
.
timeDisplay
);
mPickTime
=
(
Button
)
findViewById
(
R
.
id
.
pickTime
);
// Add a click listener to the button
mPickTime
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
public
void
onClick
(
View
v
)
{
showDialog
(
TIME_DIALOG_ID
);
}
});
// Get the current time
final
Calendar
c
=
Calendar
.
getInstance
();
mHour
=
c
.
get
(
Calendar
.
HOUR_OF_DAY
);
mMinute
=
c
.
get
(
Calendar
.
MINUTE
);
// Display the current date
updateDisplay
();
}
// The overridden method shown below gets invoked when
// showDialog() is called inside the onClick() method defined
// for handling the click event of the "Change the time" button
@Override
protected
Dialog
onCreateDialog
(
int
id
)
{
switch
(
id
)
{
case
TIME_DIALOG_ID:
return
new
TimePickerDialog
(
this
,
mTimeSetListener
,
mHour
,
mMinute
,
false
);
}
return
null
;
}
// Update the time we display in the TextView
private
void
updateDisplay
()
{
mTimeDisplay
.
setText
(
new
StringBuilder
()
.
append
(
pad
(
mHour
)).
append
(
":"
)
.
append
(
pad
(
mMinute
)));
}
// The callback received when the user "sets" the time in the dialog
private
TimePickerDialog
.
OnTimeSetListener
mTimeSetListener
=
new
TimePickerDialog
.
OnTimeSetListener
()
{
public
void
onTimeSet
(
TimePicker
view
,
int
hourOfDay
,
int
minute
)
{
mHour
=
hourOfDay
;
mMinute
=
minute
;
updateDisplay
();
}
};
private
static
String
pad
(
int
c
)
{
if
(
c
>=
10
)
return
String
.
valueOf
(
c
);
else
return
"0"
+
String
.
valueOf
(
c
);
}
}
Figure 7-5 shows the timepicker that appears onscreen after the user clicks the “Change the time” button.
Wagied Davids
You can download Android-Wheel
from the Google Code Archive. Unfortunately, installation requires more than just installing a JAR file in your libs directory, because resources needed for drawing must be in the res directory. You can extract the android-wheel-xx.zip file and copy the wheel/src and wheel/res folders into your project. Alternatively, create a new Android project from the wheel subdirectory (Android will automatically make it an Android library project) and make your main project depend on that (see Recipe 1.19). Then you can add one or more WheelView
objects to your Layout, using the full class name. This class and its friends are found in the kankan.wheel.widget
package; the adapters
subpackage provides the WheelViewAdapter
interface and some implementations. The widget
package provides two interfaces that follow the standard setListener
pattern on the WheelView
component:
wheel.addChangingListener(OnWheelChangedListener)
wheel.addScrollingListener(OnWheelScrollListener)
The code in Example 7-6, which comes from a medical app, lets you choose a body part and location (R or L for Right or Left, respectively). The choices are hardcoded here; in a real-world app, they would come from an XML file to allow for internationalization. The app should appear as shown in Figure 7-6.
This code uses the “kankan” wheel components, whose Java top-level package is kankan.wheel.widget
and whose Maven/Gradle coordinates are com.googlecode.android-wheel:datetime-picker:1.1
.
public
class
WheelDemoActivity
extends
Activity
{
private
final
static
String
TAG
=
"WheelDemo"
;
private
final
static
String
[]
wheelMenu1
=
{
"Right Arm"
,
"Left Arm"
,
"R-Abdomen"
,
"L-Abdomen"
,
"Right Thigh"
,
"Left Thigh"
};
private
final
static
String
[]
wheelMenu2
=
{
"Upper"
,
"Middle"
,
"Lower"
};
private
final
static
String
[]
wheelMenu3
=
{
"R"
,
"L"
};
// Wheel scrolled flag
private
boolean
wheelScrolled
=
false
;
private
TextView
resultText
;
private
EditText
text1
;
private
EditText
text2
;
private
EditText
text3
;
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
activity_wheel_picker
);
initWheel
(
R
.
id
.
p1
,
wheelMenu1
);
initWheel
(
R
.
id
.
p2
,
wheelMenu2
);
initWheel
(
R
.
id
.
p3
,
wheelMenu3
);
text1
=
(
EditText
)
this
.
findViewById
(
R
.
id
.
r1
);
text2
=
(
EditText
)
this
.
findViewById
(
R
.
id
.
r2
);
text3
=
(
EditText
)
this
.
findViewById
(
R
.
id
.
r3
);
resultText
=
(
TextView
)
this
.
findViewById
(
R
.
id
.
resultText
);
}
/**
* Wheel scrolled listener
*/
OnWheelScrollListener
scrolledListener
=
new
OnWheelScrollListener
()
{
@Override
public
void
onScrollingStarted
(
WheelView
wheel
)
{
wheelScrolled
=
true
;
}
@Override
public
void
onScrollingFinished
(
WheelView
wheel
)
{
wheelScrolled
=
false
;
updateStatus
();
}
};
/**
* Wheel changed listener
*/
private
final
OnWheelChangedListener
changedListener
=
new
OnWheelChangedListener
()
{
@Override
public
void
onChanged
(
WheelView
wheel
,
int
oldValue
,
int
newValue
)
{
Log
.
d
(
TAG
,
"onChanged, wheelScrolled = "
+
wheelScrolled
);
if
(!
wheelScrolled
)
{
updateStatus
();
}
}
};
/**
* Updates entered status
*/
private
void
updateStatus
()
{
text1
.
setText
(
wheelMenu1
[((
WheelView
)
findViewById
(
R
.
id
.
p1
)).
getCurrentItem
()]);
text2
.
setText
(
wheelMenu2
[((
WheelView
)
findViewById
(
R
.
id
.
p2
)).
getCurrentItem
()]);
text3
.
setText
(
wheelMenu3
[((
WheelView
)
findViewById
(
R
.
id
.
p3
)).
getCurrentItem
()]);
resultText
.
setText
(
wheelMenu1
[((
WheelView
)
findViewById
(
R
.
id
.
p1
)).
getCurrentItem
()]
+
" - "
+
wheelMenu2
[((
WheelView
)
findViewById
(
R
.
id
.
p2
)).
getCurrentItem
()]
+
" - "
+
wheelMenu3
[((
WheelView
)
findViewById
(
R
.
id
.
p3
)).
getCurrentItem
()]);
}
/**
* Initializes one wheel
* @param id
* the wheel widget ID
*/
private
void
initWheel
(
int
id
,
String
[]
wheelMenu1
)
{
WheelView
wheel
=
(
WheelView
)
findViewById
(
id
);
wheel
.
setViewAdapter
(
new
ArrayWheelAdapter
<
String
>(
this
,
wheelMenu1
));
wheel
.
setVisibleItems
(
2
);
wheel
.
setCurrentItem
(
0
);
wheel
.
addChangingListener
(
changedListener
);
wheel
.
addScrollingListener
(
scrolledListener
);
}
}
The source code for this example is in the Android Cookbook repository, in the subdirectory WheelPickerDemo (see “Getting and Using the Code Examples”).
Rachee Singh
Use a tabbed layout within a custom dialog.
The CustomDialog
class extends the Dialog
class:
public
class
CustomDialog
extends
Dialog
The constructor of the class has to be initialized:
public
CustomDialog
(
final
Context
context
)
{
super
(
context
);
setTitle
(
"My First Custom Tabbed Dialog"
);
setContentView
(
R
.
layout
.
custom_dialog_layout
);
To create two tabs, insert the Example 7-7 code within the constructor: place tab_image1
and tab_image2
in /res/drawable. These images are placed on the tabs of the tabbed custom dialog.
// Get our tabHost from the xml
TabHost
tabHost
=
(
TabHost
)
findViewById
(
R
.
id
.
TabHost01
);
tabHost
.
setup
();
// Create tab 1
TabHost
.
TabSpec
spec1
=
tabHost
.
newTabSpec
(
"tab1"
);
spec1
.
setIndicator
(
"Profile"
,
context
.
getResources
().
getDrawable
(
R
.
drawable
.
tab_image1
));
spec1
.
setContent
(
R
.
id
.
TextView01
);
tabHost
.
addTab
(
spec1
);
// Create tab2
TabHost
.
TabSpec
spec2
=
tabHost
.
newTabSpec
(
"tab2"
);
spec2
.
setIndicator
(
"Profile"
,
context
.
getResources
().
getDrawable
(
R
.
drawable
.
tab_image2
));
spec2
.
setContent
(
R
.
id
.
TextView02
);
tabHost
.
addTab
(
spec2
);
This is a simple tabbed dialog. It requires the addition of just a few lines to the constructor’s code. To implement something like a list view, a list view adapter would be required. A variety of tabs can be inserted based on the application’s requirements.
As shown in Example 7-8, the XML code for a tabbed dialog requires TabHost
tags enclosing the entire layout. Within these tags you place the locations of various parts of the tabbed dialog. You must use a frame layout to place the content of the different tabs. In this case, we are creating two tabs, both with a scroll view containing text (stored in strings.xml and named lorem_ipsum).
<TabHost
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id=
"@+id/TabHost01"
android:layout_width=
"fill_parent"
android:layout_height=
"500dip"
>
<LinearLayout
android:orientation=
"vertical"
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:padding=
"5dp"
>
<TabWidget
android:id=
"@android:id/tabs"
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
/>
<FrameLayout
android:id=
"@android:id/tabcontent"
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:padding=
"5dp"
>
<ScrollView
android:id=
"@+id/ScrollView01"
android:layout_width=
"wrap_content"
android:layout_height=
"200px"
>
<TextView
android:id=
"@+id/TextView01"
android:text=
"@string/lorem_ipsum"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:gravity=
"center_horizontal"
android:paddingLeft=
"15dip"
android:paddingTop=
"15dip"
android:paddingRight=
"20dip"
android:paddingBottom=
"15dip"
/>
</ScrollView>
<ScrollView
android:id=
"@+id/ScrollView02"
android:layout_width=
"wrap_content"
android:layout_height=
"200px"
>
<TextView
android:id=
"@+id/TextView02"
android:text=
"@string/lorem_ipsum"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:gravity=
"center_horizontal"
android:paddingLeft=
"15dip"
android:paddingTop=
"15dip"
android:paddingRight=
"20dip"
android:paddingBottom=
"15dip"
/>
</ScrollView>
</FrameLayout>
</LinearLayout>
</TabHost>
The source code for this example is in the Android Cookbook repository, in the subdirectory TabHostDemo (see “Getting and Using the Code Examples”).
Rachee Singh
In this recipe we will provide a button that shows a ProgressDialog
when clicked. In the ProgressDialog
we set the title as “Please Wait” and the content as “Processing Information.” After this we create a new thread and start the thread’s execution. In the run()
method (which gets executed once the thread gets started) we call the sleep()
method for four seconds. After these four seconds expire the ProgressDialog
is dismissed and the text in the TextView
gets changed:
complete
=
(
TextView
)
this
.
findViewById
(
R
.
id
.
complete
);
complete
.
setText
(
"Press the Button to start Processing"
);
processing
=
(
Button
)
findViewById
(
R
.
id
.
processing
);
processing
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
v
)
{
progressDialog
=
ProgressDialog
.
show
(
ProgressDialogExp
.
this
,
"Please Wait"
,
"Processing Information..."
,
true
,
false
);
Thread
thread
=
new
Thread
(
ProgressDialogExp
.
this
);
thread
.
start
();
}
});
We use a Handler
to update the UI once thread execution finishes. We send an empty message to the Handler
after thread execution completes, and then in the Handler
we dismiss the ProgressDialog
and update the text of the TextView
:
public
void
run
()
{
try
{
Thread
.
sleep
(
4000
);
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
handler
.
sendEmptyMessage
(
0
);
}
private
Handler
handler
=
new
Handler
()
{
@Override
public
void
handleMessage
(
Message
msg
)
{
progressDialog
.
dismiss
();
complete
.
setText
(
"Processing Finished"
);
}
};
The source code for this project is in the Android Cookbook repository, in the subdirectory ProgressDialogDemo (see “Getting and Using the Code Examples”).
Rachee Singh
Create a custom dialog with tabs. Since everything can be squeezed into a dialog in place of an entire Activity, the application will seem more compact.
The CustomDialog
class can directly extend Dialog
:
public
class
CustomDialog
extends
Dialog
The following lines of code in the CustomDialog
class’s onCreate()
method add a title and get handles for the buttons in the dialog:
setTitle
(
"Dialog Title"
);
setContentView
(
R
.
layout
.
custom_dialog_layout
);
// OnClickListeners for the buttons present in the Dialog
Button
button1
=
(
Button
)
findViewById
(
R
.
id
.
button1
);
Button
button2
=
(
Button
)
findViewById
(
R
.
id
.
button2
);
For the two buttons that are added, OnClickListener
s are defined in the next lines of code. On being clicked, button1
dismisses the dialog and button2
starts a new Activity:
button1
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
v
)
{
dismiss
();
// To dismiss the Dialog
}
});
button2
.
setOnClickListener
(
new
View
.
OnClickListener
()
{
@Override
public
void
onClick
(
View
v
)
{
// Fire an Intent on click of this button
Intent
showQuickInfo
=
New
Intent
(
"com.android.oreilly.QuickInfo"
);
showQuickInfo
.
setFlags
(
Intent
.
FLAG_ACTIVITY_NEW_TASK
);
context
.
startActivity
(
showQuickInfo
);
}
});
Here is the XML layout of the dialog, present in /res/layout custom_dialog_layout.xml. The entire code is enclosed in a LinearLayout
. Within the LinearLayout
, a RelativeLayout
is used to position two buttons. Then, below the RelativeLayout
is another RelativeLayout
containing a scroll view. android_button and thumbsup are the names of the images in /res/drawable:
<LinearLayout
android:orientation=
"vertical"
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:padding=
"5dp"
>
<RelativeLayout
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:paddingBottom=
"10dip"
>
<Button
android:id=
"@+id/button1"
android:background=
"@drawable/android_button"
android:layout_height=
"80dip"
android:layout_width=
"80dip"
android:layout_alignParentLeft=
"true"
android:layout_marginLeft=
"10dip"
android:gravity=
"center"
/>
<Button
android:id=
"@+id/button2"
android:background=
"@drawable/thumbsup"
android:layout_height=
"80dip"
android:layout_width=
"80dip"
android:layout_alignParentRight=
"true"
android:layout_marginRight=
"10dip"
android:gravity=
"center"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width=
"fill_parent"
android:layout_height=
"wrap_content"
android:paddingBottom=
"10dip"
>
<ScrollView
android:id=
"@+id/ScrollView01"
android:layout_width=
"wrap_content"
android:layout_height=
"200px"
>
<TextView
android:id=
"@+id/TextView01"
android:text=
"@string/lorem"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:gravity=
"center_horizontal"
android:paddingLeft=
"15dip"
android:paddingTop=
"15dip"
android:paddingRight=
"20dip"
android:paddingBottom=
"15dip"
/>
</ScrollView>
</RelativeLayout>
</LinearLayout>
Daniel Fowler
Write an AboutBox
class that can be installed into any new app.
Whatever the operating system, whatever the program, chances are it has an About option. This is useful for support:
Since it is likely to be required again and again, it is worth having a ready-made AboutBox
class that you can easily add to any new app that you develop. At a minimum, the About option should display a dialog with a title (such as About My App
), the version name from the manifest, some descriptive text (loaded from a string resource), and an OK button.
The version name can be read from the PackageInfo
class. (PackageInfo
is obtained from PackageManager
, which itself is available from the app’s Context
). Here is a method to read an app’s version name string:
static
String
VersionName
(
Context
context
)
{
try
{
return
context
.
getPackageManager
().
getPackageInfo
(
context
.
getPackageName
(),
0
).
versionName
;
}
catch
(
NameNotFoundException
e
)
{
return
"Unknown"
;
}
}
PageInfo
can throw a NameNotFoundException
(for when the class is used to find information on other packages). The exception is unlikely to occur; here it is just consumed by returning an error string. (To return the version code, the app’s internal version number, swap versionName
for versionCode
and return an integer.)
With an AlertDialog.Builder
and the setTitle()
, setMessage()
, and show()
methods, you will soon have an About option up and running; but you can improve the About option by using the Android Linkify
class and a custom layout. In the About text, any web addresses (such as app help pages on the web) and email addresses (useful for a support email link) can be made clickable. The layout shown in Example 7-9 is the contents of the file aboutbox.xml.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id=
"@+id/aboutView"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
>
<LinearLayout
android:id=
"@+id/aboutLayout"
android:orientation=
"horizontal"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:padding=
"5dp"
>
<TextView
android:id=
"@+id/aboutText"
android:layout_width=
"wrap_content"
android:layout_height=
"fill_parent"
android:textColor=
"#888"
/>
</LinearLayout>
</ScrollView>
A ScrollView
is required for when the About text is long and the screens are small (e.g., QVGA; see Table 5-2).
The AboutBox
class uses a Spannable
to hold the text; the TextView
containing the spanned text is be passed to android.text.util.Linkify
. The layout is inflated, the About text is set, and then AlertBuilder.Builder
is used to create the dialog. Example 7-10 shows the full code for the AboutBox
class.
You could also load the text from a static HTML file shipped with the application (see Recipe 10.3).
public
class
AboutBox
{
static
String
VersionName
(
Context
context
)
{
try
{
return
context
.
getPackageManager
().
getPackageInfo
(
context
.
getPackageName
(),
0
).
versionName
;
}
catch
(
NameNotFoundException
e
)
{
return
"Unknown"
;
}
}
public
static
void
show
(
Activity
callingActivity
)
{
// Use a Spannable to allow for link highlighting
SpannableString
aboutText
=
new
SpannableString
(
"Version "
+
VersionName
(
callingActivity
)+
" "
+
callingActivity
.
getString
(
R
.
string
.
about
));
// Generate views to pass to AlertDialog.Builder and to set the text
View
about
;
TextView
tvAbout
;
try
{
// Inflate the custom view
LayoutInflater
inflater
=
callingActivity
.
getLayoutInflater
();
about
=
inflater
.
inflate
(
R
.
layout
.
aboutbox
,
(
ViewGroup
)
callingActivity
.
findViewById
(
R
.
id
.
aboutView
));
tvAbout
=
(
TextView
)
about
.
findViewById
(
R
.
id
.
aboutText
);
}
catch
(
InflateException
e
)
{
// Unchecked exception - unlikely, but default to TextView if it occurs
about
=
tvAbout
=
new
TextView
(
callingActivity
);
}
// Set the about text
tvAbout
.
setText
(
aboutText
);
// Now Linkify the text
Linkify
.
addLinks
(
tvAbout
,
Linkify
.
ALL
);
// Build and show the dialog
new
AlertDialog
.
Builder
(
callingActivity
)
.
setTitle
(
"About "
+
callingActivity
.
getString
(
R
.
string
.
app_name
))
.
setCancelable
(
true
)
.
setIcon
(
R
.
drawable
.
icon
)
.
setPositiveButton
(
"OK"
,
null
)
.
setView
(
about
)
.
show
();
// Builder method returns allow for method chaining
}
}
The app’s icon can be shown in the About box title using setIcon(R.drawable.icon)
.
String resources for the About text can be placed in a separate file for easier maintenance, such as res/values/about_strings.xml. The name of this file is irrelevant, as string resources are identified by their ID:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string
name=
"about"
>
This is our App, please see http://www.example.com. Email support at [email protected].</string>
</resources>
Showing the About box requires only one line of code, shown here on a button click:
public
class
Main
extends
Activity
{
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
main
);
findViewById
(
R
.
id
.
button1
).
setOnClickListener
(
new
OnClickListener
()
{
public
void
onClick
(
View
v
)
{
AboutBox
.
show
(
Main
.
this
);
}
});
}
}
The result should look something like Figure 7-7.
To reuse this About box, just copy the aboutbox.xml file into a project’s res/layout folder, copy about_strings.xml to res/values (adjusting its text as needed), and copy the AboutBox.java file into the source folder (adjusting the package name as needed). Then call AboutBox.show()
from a button or menu listener. Web addresses and email addresses highlighted in the text can be clicked and invoke the browser or email client, which makes it easier for the user to contact you using those means.
The source code for this project is in the Android Cookbook repository, in the subdirectory AboutBoxDemo (see “Getting and Using the Code Examples”).
Ian Darwin
Create a Notification
object, and provide it with a PendingIntent
that wraps a real Intent
for what to do when the user selects the notification. At the same time you pass in the PendingIntent
you also pass a title and text to be displayed in the notification area. You should set the AUTO_CANCEL
flag unless you want to remove the notification from the status bar manually. Finally, find and ask the NotificationManager
to display (notify) your notification, associating with it an ID so that you can refer to it later (e.g., to remove it).
Notifications are normally used from a running Service
class to notify (hence the name) the user of some fact, either because an event has occurred (receipt of a message, loss of contact with a server, or whatever) or just to remind the user that a long-running Service is still running. The notification is commonly used to start an Activity and is, in fact, the only recommended way for a background Service to start an Activity (Services should never start Activities directly!).
Create a Notification
object; the constructor takes an Icon
ID, the text to display briefly in the status bar, and the time at which the event occurred (a timestamp in milliseconds). Before you can show the notification, you have to provide it with a PendingIntent
for what to do when the user selects the notification, and ask the NotificationManager
to display your notification. Example 7-11 shows the notification code.
The following code shows doing the right thing in the (usually) wrong place. Notifications are frequently shown from Services; this recipe just focuses on the Notification API.
public
class
Main
extends
Activity
{
private
static
final
int
NOTIFICATION_ID
=
1
;
/** Called when the Activity is first created. */
@Override
public
void
onCreate
(
Bundle
savedInstanceState
)
{
super
.
onCreate
(
savedInstanceState
);
setContentView
(
R
.
layout
.
main
);
int
icon
=
R
.
drawable
.
icon
;
// Preferably a distinct icon
// Create the notification itself
String
noticeMeText
=
getString
(
R
.
string
.
noticeMe
);
Notification
n
=
new
Notification
(
icon
,
noticeMeText
,
System
.
currentTimeMillis
());
// And the Intent of what to do when user selects notification
Context
applicationContext
=
getApplicationContext
();
Intent
notifyIntent
=
new
Intent
(
this
,
NotificationTarget
.
class
);
PendingIntent
wrappedIntent
=
PendingIntent
.
getActivity
(
this
,
0
,
notifyIntent
,
Intent
.
FLAG_ACTIVITY_NEW_TASK
);
// Condition the notification
String
title
=
getString
(
R
.
string
.
title
);
String
message
=
getString
(
R
.
string
.
message
);
n
.
setLatestEventInfo
(
applicationContext
,
title
,
message
,
wrappedIntent
);
n
.
flags
|=
Notification
.
FLAG_AUTO_CANCEL
;
// Now invoke the Notification service
String
notifService
=
Context
.
NOTIFICATION_SERVICE
;
NotificationManager
mgr
=
(
NotificationManager
)
getSystemService
(
notifService
);
mgr
.
notify
(
NOTIFICATION_ID
,
n
);
}
}
The following is the file strings.xml:
<resources>
<string
name=
"app_name"
>
NotificationDemo</string>
<string
name=
"hello"
>
Hello World, Main!</string>
<string
name=
"noticeMe"
>
Lookie Here!!</string>
<string
name=
"title"
>
My Notification</string>
<string
name=
"message"
>
This is my message</string>
<string
name=
"target_name"
>
Notification Target</string>
<string
name=
"thanks"
>
Thank you for selecting the notification.</string>
</resources>
The noticeMe
string may appear briefly (only for a few seconds) in the status bar. Notification text and icons appear in the very upper left of the screen, as shown in Figure 7-8.
The tiny Android logo is this application’s icon.
When the user drags the status bar down, it expands to show the details, which include the icons and the title and message strings (see Figure 7-9). You can also use a custom view here; refer to the official Android documentation.
If you have auto-clear set, the notification will no longer appear in the status bar. If the user selects the notification box, the PendingIntent
becomes current. Ours simply shows a basic “Thank you” notification (Figure 7-10). If the user clicks the Clear button, however, the Intent does not get run (even with auto-clear, which can leave you in a bit of a lurch).
If the user’s attention is needed right away, you can specify a sound to be played when the notification is first displayed. Or you can make the device vibrate, where supported.
The user’s default notification sound can be played as follows:
notification
.
defaults
|=
Notification
.
DEFAULT_SOUND
;
Alternatively, you can provide a Uri
to a sound file, either on the SD card or in your application:
notification
.
sound
=
Uri
.
parse
(
"file:///sdcard/mydata/annoy_the_user.mp3"
);
Note that if you both set DEFAULT_SOUND
and provide a “sound” URI, only the default will be used.
To really annoy the user, you can make the sound play repeatedly; just add the flag FLAG_INSISTENT
to the flags
field:
notification
.
defaults
|=
Notification
.
FLAG_INSISTENT
;
Invoking device vibration when your notification is displayed is as simple as:
notification
.
defaults
|=
Notification
.
DEFAULT_VIBRATE
;
As a final flourish, on devices with a signaling LED (on most phones it’s near the bottom of the physical screen or otherwise in the controls area), you can make the LED flash in various colors and patterns. At a bare minimum, you need:
notification
.
ledARGB
=
color
;
notification
.
defaults
|
=
Notification
.
FLAGS_SHOW_LIGHTS
;
The +color+ is a four-byte integer containing, as the name suggests, alpha (transparency), red, green, and blue values. This is similar to traditional web color syntax, but for the transparency part; thus, 0xff0000ff
is bright blue (full opacity/no transparency; no red or green).
You can also specify a flashing pattern using notification.ledOnMS
and notification.ledOffMS
, which are the times in milliseconds for the LED to be on and off as it flashes. Again, if you set any of these values but don’t specify FLAGS_SHOW_LIGHTS
, nothing will happen.
The developer documentation on notifications.
The source code for this example is in the Android Cookbook repository, in the subdirectory NotificationDemo (see “Getting and Using the Code Examples”).
44.220.184.63