WHAT YOU WILL LEARN IN THIS CHAPTER
The various ViewGroups you can use to lay out your views
How to adapt to changes in screen orientation
How to manage screen orientation changes
How to create the UI programmatically
How to listen for UI notifications
In Chapter 2, you learned about the Activity
class and its life cycle. You learned that an activity is a means by which users interact with the application. However, an activity by itself does not have a presence on the screen. Instead, it has to draw the screen using Views and ViewGroups. In this chapter, you will learn the details about creating user interfaces in Android, how users interact with them. In addition, you will learn how to handle changes in screen orientation on your Android devices.
In Chapter 2, you saw that the basic unit of an Android application is an activity. An activity displays the user interface of your application, which may contain widgets like buttons, labels, text boxes, and so on. Typically, you define your UI using an XML file (e.g., the main.xml
file located in the res/layout
folder), which may look like this:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
During run time, you load the XML UI in the onCreate()
event handler in your Activity
class, using the setContentView()
method of the Activity
class:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main;
}
During compilation, each element in the XML file is compiled into its equivalent Android GUI class, with attributes represented by methods. The Android system then creates the UI of the activity when it is loaded.
While it is always easier to build your UI using an XML file, sometimes you need to build your UI dynamically during run time (for example, when writing games). Hence, it is also possible to create your UI entirely using code. Later in this chapter you will see an example of how this can be done.
An activity contains Views and ViewGroups. A view is a widget that has an appearance on screen. Examples of views are buttons, labels, and text boxes. A view derives from the base class android.view.View
.
Chapters 4 and 5 discuss the various common views in Android.
One or more views can be grouped together into a ViewGroup. A ViewGroup (which is itself a special type of view) provides the layout in which you can order the appearance and sequence of views. Examples of ViewGroups include LinearLayout
and FrameLayout
. A ViewGroup derives from the base class android.view.ViewGroup
.
Android supports the following ViewGroups:
The following sections describe each of these ViewGroups in more detail. Note that in practice it is common to combine different types of layouts to create the UI you want.
The LinearLayout
arranges views in a single column or a single row. Child views can be arranged either vertically or horizontally. To see how LinearLayout
works, consider the following elements typically contained in the main.xml
file:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout>
In the main.xml
file, observe that the root element is <LinearLayout>
and it has a <TextView>
element contained within it. The <LinearLayout>
element controls the order in which the views contained within it appear.
Each View and ViewGroup has a set of common attributes, some of which are described in Table 3-1.
Table 3-1. Common Attributes Used in Views and ViewGroups
ATTRIBUTE | DESCRIPTION |
---|---|
Specifies the width of the View or ViewGroup | |
Specifies the height of the View or ViewGroup | |
Specifies extra space on the top side of the View or ViewGroup | |
Specifies extra space on the bottom side of the View or ViewGroup | |
Specifies extra space on the left side of the View or ViewGroup | |
Specifies extra space on the right side of the View or ViewGroup | |
Specifies how child Views are positioned | |
Specifies how much of the extra space in the layout should be allocated to the View | |
Specifies the x-coordinate of the View or ViewGroup | |
Specifies the y-coordinate of the View or ViewGroup |
Some of these attributes are applicable only when a View is in a specific ViewGroup. For example, the layout_weight
and layout_gravity
attributes are applicable only when a View is in either a LinearLayout
or a TableLayout
.
For example, the width of the <TextView>
element fills the entire width of its parent (which is the screen in this case) using the fill_parent
constant. Its height is indicated by the wrap_content
constant, which means that its height is the height of its content (in this case, the text contained within it). If you don't want to have the <TextView>
view occupy the entire row, you can set its layout_width
attribute to wrap_content
, like this:
< TextView android:layout_width="wrap_content android:layout_height="wrap_content" android:text="@string/hello" />
This will set the width of the view to be equal to the width of the text contained within it.
Consider the following layout:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="105dp" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:layout_width="160dp" android:layout_height="wrap_content" android:text="Button" /> </LinearLayout>
Here, you set the width of both the TextView
and Button
views to an absolute value. In this case, the width for the TextView
is set to 105 density-independent pixels wide, and the Button
to 160 density-independent pixels wide. Figure 3-1 shows how the views look when viewed on an emulator with a resolution of 320×480.
Figure 3-2 shows how the views look when viewed on a high-resolution (480×800) emulator.
As you can see, in both emulators the widths of both views are the same with respect to the width of the emulator. This demonstrates the usefulness of using the dp
unit, which ensures that even if the resolution of the target device is different, the size of the view relative to the device remains unchanged.
The preceding example also specifies that the orientation of the layout is vertical:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
The default orientation layout is horizontal, so if you omit the android:orientation
attribute, the views will appear as shown in Figure 3-3.
In LinearLayout
, you can apply the layout_weight
and layout_gravity
attributes to views contained within it, as the following modifications to main.xml
show:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="105dp" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:layout_width="160dp" android:layout_height="wrap_content" android:text="Button"
android:layout_gravity="right"
android:layout_weight="0.2"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_weight="0.8"
/> </LinearLayout>
Figure 3-4 shows that the button is aligned to the right of its parent (which is the LinearLayout
) using the layout_gravity
attribute. At the same time, you use the layout_weight
attribute to specify the ratio in which the Button
and EditText
views occupy the remaining space on the screen. The total value for the layout_weight
attribute must be equal to 1.
The AbsoluteLayout
enables you to specify the exact location of its children. Consider the following UI defined in main.xml
:
<?xml version="1.0" encoding="utf-8"?> <AbsoluteLayout android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"
> <Button android:layout_width="188dp" android:layout_height="wrap_content" android:text="Button" android:layout_x="126px" android:layout_y="361px" /> <Button android:layout_width="113dp" android:layout_height="wrap_content" android:text="Button" android:layout_x="12px" android:layout_y="361px" /> </AbsoluteLayout>
Figure 3-5 shows the two Button
views located at their specified positions using the android_layout_x
and android_layout_y
attributes.
However, there is a problem with the AbsoluteLayout
when the activity is viewed on a high-resolution screen (see Figure 3-6). For this reason, the AbsoluteLayout
has been deprecated since Android 1.5 (although it is still supported in the current version). You should avoid using the AbsoluteLayout
in your UI, as it is not guaranteed to be supported in future versions of Android. You should instead use the other layouts described in this chapter.
The TableLayout
groups views into rows and columns. You use the <TableRow>
element to designate a row in the table. Each row can contain one or more views. Each view you place within a row forms a cell. The width of each column is determined by the largest width of each cell in that column.
Consider the content of main.xml
shown here:
<?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:layout_width="fill_parent" > <TableRow> <TextView android:text="User Name:" android:width ="120px" /> <EditText android:id="@+id/txtUserName" android:width="200px" /> </TableRow>
<TableRow> <TextView android:text="Password:" /> <EditText android:id="@+id/txtPassword" android:password="true" /> </TableRow> <TableRow> <TextView /> <CheckBox android:id="@+id/chkRememberPassword" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Remember Password" /> </TableRow> <TableRow> <Button android:id="@+id/buttonSignIn" android:text="Log In" /> </TableRow> </TableLayout>
Figure 3-7 shows what the preceding looks like when rendered on the Android Emulator.
Note that in the preceding example, there are two columns and four rows in the TableLayout
. The cell directly under the Password TextView
is populated with an <TextView/>
empty element. If you don't do this, the Remember Password checkbox will appear under the Password TextView
, as shown in Figure 3-8.
The RelativeLayout
enables you to specify how child views are positioned relative to each other. Consider the following main.xml
file:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/RLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/lblComments" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Comments"android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
/> <EditText android:id="@+id/txtComments" android:layout_width="fill_parent" android:layout_height="170px" android:textSize="18sp"android:layout_alignLeft="@+id/lblComments"
android:layout_below="@+id/lblComments"
android:layout_centerHorizontal="true"
/> <Button android:id="@+id/btnSave"
android:layout_width="125px" android:layout_height="wrap_content" android:text="Save"android:layout_below="@+id/txtComments"
android:layout_alignRight="@+id/txtComments"
/> <Button android:id="@+id/btnCancel" android:layout_width="124px" android:layout_height="wrap_content" android:text="Cancel"android:layout_below="@+id/txtComments"
android:layout_alignLeft="@+id/txtComments"
/> </RelativeLayout>
Notice that each view embedded within the RelativeLayout
has attributes that enable it to align with another view. These attributes are as follows:
The value for each of these attributes is the ID for the view that you are referencing. The preceding XML UI creates the screen shown in Figure 3-9.
The FrameLayout
is a placeholder on screen that you can use to display a single view. Views that you add to a FrameLayout
are always anchored to the top left of the layout. Consider the following content in main.xml
:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/RLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/lblComments" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="This is my lovely dog, Ookii" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/lblComments" android:layout_below="@+id/lblComments" android:layout_centerHorizontal="true" > <ImageView android:src = "@drawable/ookii" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> </RelativeLayout>
Here, you have a FrameLayout
within a RelativeLayout
. Within the FrameLayout
, you embed an ImageView
. The UI is shown in Figure 3-10.
This example assumes that the res/drawable-mdpi
folder has an image named ookii.png
.
If you add another view (such as a Button
view) within the FrameLayout
, the view will overlap the previous view (see Figure 3-11):
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/RLayout" android:layout_width="fill_parent" android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android" > <TextView android:id="@+id/lblComments" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="This is my lovely dog, Ookii" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" /> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/lblComments" android:layout_below="@+id/lblComments" android:layout_centerHorizontal="true" > <ImageView android:src = "@drawable/ookii" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Buttonandroid:layout_width="124dp"
android:layout_height="wrap_content"
android:text="Print Picture"
/> </FrameLayout> </RelativeLayout>
A ScrollView
is a special type of FrameLayout
in that it enables users to scroll through a list of views that occupy more space than the physical display. The ScrollView
can contain only one child view or ViewGroup, which normally is a LinearLayout
.
Do not use a ListView
(discussed in Chapter 4) together with the ScrollView
. The ListView
is designed for showing a list of related information and is optimized for dealing with large lists.
The following main.xml
content shows a ScrollView
containing a LinearLayout
, which in turn contains some Button
and EditText
views:
<?xml version="1.0" encoding="utf-8"?> <ScrollView android:layout_width="fill_parent"
android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 1" /> <Button android:id="@+id/button2" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 2" /> <Button android:id="@+id/button3" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 3" /> <EditText android:id="@+id/txt" android:layout_width="fill_parent" android:layout_height="300px" /> <Button android:id="@+id/button4" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 4" /> <Button android:id="@+id/button5" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Button 5" /> </LinearLayout> </ScrollView>
Figure 3-12 shows the ScrollView
enabling the users to drag the screen upward to reveal the views located at the bottom of the screen.
One of the key features of modern smartphones is their ability to switch screen orientation, and Android is no exception. Android supports two screen orientations: portrait and landscape. By default, when you change the display orientation of your Android device, the current activity that is displayed will automatically redraw its content in the new orientation. This is because the onCreate()
event of the activity is fired whenever there is a change in display orientation.
When you change the orientation of your Android device, your current activity is actually destroyed and then re-created.
However, when the views are redrawn, they may be drawn in their original locations (depending on the layout selected). Figure 3-13 shows one of the examples illustrated earlier displayed in both portrait and landscape mode.
As you can observe in landscape mode, a lot of empty space on the right of the screen could be used. Furthermore, any additional views at the bottom of the screen would be hidden when the screen orientation is set to landscape.
In general, you can employ two techniques to handle changes in screen orientation:
Anchoring — The easiest way is to "anchor" your views to the four edges of the screen. When the screen orientation changes, the views can anchor neatly to the edges.
Resizing and repositioning — Whereas anchoring and centralizing are simple techniques to ensure that views can handle changes in screen orientation, the ultimate technique is resizing each and every view according to the current screen orientation.
Anchoring could be easily achieved by using RelativeLayout
. Consider the following main.xml
containing five Button
views embedded within the <RelativeLayout>
element:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Top Left Button"android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
/>
<Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Top Right Button"android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
/> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottom Left Button"android:layout_alignParentLeft="true"
android:layout_alignParentBottom="true"
/> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottom Right Button"android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
/> <Button android:id="@+id/button5" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Middle Button"android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
/> </RelativeLayout>
Observe the following attributes found in the various Button
views:
layout_alignParentLeft
— Aligns the view to the left of the parent view
layout_alignParentRight
— Aligns the view to the right of the parent view
layout_alignParentTop
— Aligns the view to the top of the parent view
layout_alignParentBottom
— Aligns the view to the bottom of the parent view
layout_centerVertical
— Centers the view vertically within its parent view
layout_centerHorizontal
— Centers the view horizontally within its parent view
Figure 3-14 shows the activity when viewed in portrait mode.
When the screen orientation changes to landscape mode, the four buttons are aligned to the four edges of the screen, and the center button is centered in the middle of the screen with its width fully stretched (see Figure 3-15).
Apart from anchoring your views to the four edges of the screen, an easier way to customize the UI based on screen orientation is to create a separate res/layout
folder containing the XML files for the UI of each orientation. To support landscape mode, you can create a new folder in the res
folder and name it as layout-land
(representing landscape). Figure 3-16 shows the new folder containing the file main.xml
.
Basically, the main.xml
file contained within the layout
folder defines the UI for the activity in portrait mode, whereas the main.xml
file in the layout-land
folder defines the UI in landscape mode.
The following shows the content of main.xml
under the layout
folder:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Top Left Button" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Top Right Button" android:layout_alignParentTop="true" android:layout_alignParentRight="true" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottom Left Button" android:layout_alignParentLeft="true" android:layout_alignParentBottom="true" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottom Right Button" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" />
<Button android:id="@+id/button5" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="Middle Button" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> </RelativeLayout>
The following shows the content of main.xml
under the layout-land
folder (the statements in bold are the additional views to display in landscape mode):
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Top Left Button" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Top Right Button" android:layout_alignParentTop="true" android:layout_alignParentRight="true" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottom Left Button" android:layout_alignParentLeft="true" android:layout_alignParentBottom="true" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Bottom Right Button" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" /> <Button android:id="@+id/button5" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="Middle Button" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> <Button android:id="@+id/button6" android:layout_width="180px" android:layout_height="wrap_content" android:text="Top Middle Button" android:layout_centerVertical="true" android:layout_centerHorizontal="true" android:layout_alignParentTop="true" /> <Button android:id="@+id/button7" android:layout_width="180px" android:layout_height="wrap_content" android:text="Bottom Middle Button" android:layout_centerVertical="true" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" /> </RelativeLayout>
When the activity is loaded in portrait mode, it will show five buttons, as shown in Figure 3-17.
When the activity is loaded in landscape mode, there are now seven buttons (see Figure 3-18), proving that different XML files are loaded when the device is in a different orientation.
Using this method, when the orientation of the device changes, Android will automatically load the appropriate XML file for your activity depending on the current screen orientation.
Now that you have looked at how to implement the two techniques for adapting to screen orientation changes, let's explore what happens to an activity's state when the device changes orientation.
The following Try It Out demonstrates the behavior of an activity when the device changes orientation.
So far, you have learned that changing screen orientation destroys an activity and re-creates it. Keep in mind that when an activity is re-created, the current state of the activity may be lost. When an activity is killed, it will fire one or more of the following two events:
onPause()
— This event is always fired whenever an activity is killed or pushed into the background.
onSaveInstanceState()
— This event is also fired whenever an activity is about to be killed or put into the background (just like the onPause()
event). However, unlike the onPause()
event, the onSaveInstanceState
event is not fired when an activity is being unloaded from the stack (for example, when the user pressed the Back button), because there is no need to restore its state later.
In short, to preserve the state of an activity, you could always implement the onPause()
event, and then use your own ways to preserve the state of your activity, such as using a database, internal or external file storage, etc.
If you simply want to preserve the state of an activity so that it can be restored later when the activity is re-created (such as when the device changes orientation), a much simpler way would be to implement the onSaveInstanceState()
method, as it provides a Bundle
object as an argument so that you can use it to save your activity's state. The following code shows that you can save the string ID
into the Bundle
object during the onSaveInstanceState
event:
@Override public void onSaveInstanceState(Bundle outState) { //---save whatever you need to persist--- outState.putString("ID", "1234567890"); super.onSaveInstanceState(outState); }
When an activity is re-created, the onCreate()
event is first fired, followed by the onRestoreInstanceState()
event, which enables you to retrieve the state that you saved previously in the onSaveInstanceState
event through the Bundle
object in its argument:
@Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); //---retrieve the information persisted earlier--- String ID = savedInstanceState.getString("ID"); }
Although you can use the onSaveInstanceState()
event to save state information, note the limitation that you can only save your state information into a Bundle
object. If you need to save more complex data structures, then this is not an adequate solution.
Another event handler that you can use is the onRetainNonConfigurationInstance()
event. This event is fired when an activity is about to be destroyed due to a configuration change. You can save your current data by returning it in this event, like this:
@Override public Object onRetainNonConfigurationInstance() { //---save whatever you want here; it takes in an Object type--- return("Some text to preserve"); }
When screen orientation changes, this change is part of what is known as a configuration change. A configuration change will cause your current activity to be destroyed.
Note that this event returns an Object
type, which pretty much allows you to return any data type. To extract the saved data, you can extract it in the onCreate()
event, using the getLastNonConfigurationInstance()
method, like this:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.d(StateInfo"
, "onCreate");String str = (String) getLastNonConfigurationInstance();
}
Sometimes you need to know the device's current orientation during run time. To determine that, you can use the WindowManager
class. The following code snippet demonstrates how you can programmatically detect the current orientation of your activity:
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
//... public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //---get the current display info---WindowManager wm = getWindowManager();
Display d = wm.getDefaultDisplay();
if (d.getWidth() > d.getHeight())
{
//---landscape mode---
Log
.d
("Orientation", "Landscape mode");
}
else
{
//---portrait mode---
Log
.d
("Orientation", "Portrait mode");
}
}
The getDefaultDisplay()
method returns a Display
object representing the screen of the device. You can then get its width and height and deduce the current orientation.
Occasionally you might want to ensure that your application is only displayed in a certain orientation. For example, you may be writing a game that should only be viewed in landscape mode. In this case, you can programmatically force a change in orientation using the setRequestOrientation()
method of the Activity
class:
import android.content.pm.ActivityInfo;
public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //---change to landscape mode---setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
To change to portrait mode, use the ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
constant:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
Besides using the setRequestOrientation()
method, you can also use the android:screenOrientation
attribute on the <activity>
element in AndroidManifest.xml
as follows to constrain the activity to a certain orientation:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.learn2develop.Orientations"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity> </application> <uses-sdk android:minSdkVersion="9" /> </manifest>
The preceding example constrains the activity to a certain orientation (landscape in this case) and prevents the activity from being destroyed; that is, the activity will not be destroyed and the onCreate()
event will not be fired again when the orientation of the device changes.
Following are two other values that you can specify in the android:screenOrientation
attribute:
So far, all the UIs you have seen in this chapter are created using XML. As mentioned earlier, besides using XML you can also create the UI using code. This approach is useful if your UI needs to be dynamically generated during run time. For example, suppose you are building a cinema ticket reservation system and your application will display the seats of each cinema using buttons. In this case, you would need to dynamically generate the UI based on the cinema selected by the user.
The following Try It Out demonstrates the code needed to dynamically build the UI in your activity.
Users interact with your UI at two levels: the activity level and the views level. At the activity level, the Activity
class exposes methods that you can override. Some common methods that you can override in your activities include the following:
onKeyDown
— Called when a key was pressed and not handled by any of the views contained within the activity
onKeyUp
— Called when a key was released and not handled by any of the views contained within the activity
onMenuItemSelected
— Called when a panel's menu item has been selected by the user (covered in Chapter 5)
onMenuOpened
— Called when a panel's menu is opened by the user (covered in Chapter 5)
To understand how activities interact with the user, let's start off by overriding some of the methods defined in the activity's base class and learn how they are handled when the user interacts with the activity.
Views can fire events when users interact with them. For example, when a user touches a Button
view, you need to service the event so that the appropriate action can be performed. To do so, you need to explicitly register events for views.
Using the same example discussed in the previous section, recall that the activity has two Button
views; therefore, you can register the button click events using an anonymous class as shown here:
package net.learn2develop.UIActivity; import android.app.Activity; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; import android.widget.Toast;import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);//---the two buttons are wired to the same event handler---
Button btn1 = (Button)findViewById(R.id.btn1);
btn1.setOnClickListener( btnListener);
Button btn2 = (Button)findViewById(R.id. btn2);
btn2.setOnClickListener( btnListener);
}
//---create an anonymous class to act as a button click listener---
private OnClickListener btnListener = new OnClickListener()
{
public void onClick(View v)
{
Toast
.makeText
(getBaseContext(),
((Button) v).getText() + " was clicked",
Toast.LENGTH_LONG).show();
}
};
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { //... //... } return false; } }
If you now press either the OK button or the Cancel button, the appropriate message will be displayed (see Figure 3-25), proving that the event is wired up properly.
Besides defining an anonymous class for the event handler, you can also define an anonymous inner class to handle an event. The following example shows how you can handle the onFocusChange()
event for the EditText
view:
import android.widget.EditText; public class MainActivity extends Activity {
/** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //---the two buttons are wired to the same event handler--- Button btn1 = (Button)findViewById(R.id.btn1); btn1.setOnClickListener( btnListener); Button btn2 = (Button)findViewById(R.id. btn2); btn2.setOnClickListener( btnListener);EditText txt1 = (EditText)findViewById(R.id.txt1);
//---create an anonymous inner class to act as an onfocus listener---
txt1.setOnFocusChangeListener(new View.OnFocusChangeListener()
{
@Override
public void onFocusChange(View v, boolean hasFocus) {
Toast
.makeText
(getBaseContext(),
((EditText) v).getId() + " has focus - " + hasFocus,
Toast.LENGTH_LONG).show();
}
});
}
As shown in Figure 3-26, when the EditText
view receives the focus, a message is printed on the screen.
In this chapter, you have learned how user interfaces are created in Android. You have also learned about the different layouts that you can use to position the views in your Android UI. Because Android devices support more than one screen orientation, you need to take special care to ensure that your UI can adapt to changes in screen orientation.
18.117.100.118