Developing Orientation-Aware Android Applications

ost modern, self-respecting mobile operating systems in today’s market support different screen orientations based on the position of the device. Android is no exception. While this feature is often taken for granted, it’s something on which developers spend extra time, ensuring that their applications work flawlessly regardless of screen orientation. This article will show you how screen orientation works in Android and some techniques to make your life easier.

Default Behavior for the Android G1

Create a new Android project and name it OrientationAware. For today’s current Android device (which at the time of writing is the T-Mobile G1), the screen orientation changes only when the keyboard is opened or closed. When the keyboard is closed, the screen is displayed in portrait mode; when it is opened, the screen orientation changes to landscape.

To test the concepts discussed in this article, connect the G1 to your computer. In Eclipse, press F11 to deploy the application that you have just created to the G1. Figure 1 shows the UI of the application when the keyboard is closed. When the keyboard is opened, the orientation changes automatically to landscape mode, as shown in Figure 2.


Figure 1. Portrait: Here’s the device display in portrait mode.
 
Figure 2. Landscape: Here’s the device display in landscape mode.

Figure 3. Error: This is an activity that does not know how to react to a change in orientation.

Reacting to changes in screen orientation is important because it affects the look of your UI. The left side of Figure 3 shows an application designed to display in portrait mode. However, if special care is not taken to adapt the UI for landscape mode, it will look like the right side of Figure 3 when the device changes to landscape mode.

There are three general techniques you can employ 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.
  • Centralizing: Like anchoring, if your activity has only a few views, you might want to consider centralizing your views so that they are always displayed in the center of the screen.
  • Resizing and Repositioning: Anchoring and centralizing are simple techniques to ensure views can handle changes in screen orientation. The ultimate is resizing each and every view according to the current screen orientation.

Anchoring and Centralizing
Anchoring and centralizing can be easily achieved by using RelativeLayout. Consider main.xml (see Listing 1), which contains five Button views embedded within the element.

Observe the following attributes found in the various Button views:

  • layout_alignParentLeft: This aligns the view to the left of the parent view.
  • layout_alignParentRight: This aligns the view to the right of the parent view.
  • layout_alignParentTop: This aligns the view to the top of the parent view.
  • layout_alignParentBottom: This aligns the view to the bottom of the parent view.
  • layout_centerVertical: This centers the view vertically within its parent view.
  • layout_centerHorizontal: This centers the view horizontally within its parent view.

Figure 4 shows the activity when viewed in the portrait mode.


Figure 4. Portrait Mode: The image shows the activity in portrait mode.
 
Figure 5. Landscape Mode:. This is what it looks like when all views are aligned appropriately.

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 5).

Manual Repositioning

If you want to manually reposition each view in your activity when the screen orientation changes, use AbsoluteLayout. Consider the following content in main.xml:

    

Here, the activity has only one button view as shown in Figure 6.


Figure 6. Solo: This shows the activity with one view.
 
Figure 7. Redone: The Button view is repositioned and resized when it is in landscape mode.

When the screen orientation changes to landscape mode, you might want to reposition the button view. In this case, you need to write some code to determine the current screen orientation the activity is in and then manually set the layout parameter of the view to reposition.

To see how this is done, use the code in Listing 2 in Orientation.java.

The above program first checks the current screen size and determines the orientation. It then creates a new LayoutParams object (containing the new position to locate the view) to be used by the Button view.

Figure 7 shows the application in action?the Button view is repositioned and resized when the screen orientation changes to landscape mode.

Using the layout Folder

Apart from writing code, an easier way of customizing the UI based on screen orientation is to create a separate res/layout folder containing the XML files for the UI. To support landscape mode, you can create a new folder in the res folder and name it layout-land (representing landscape). Figure 8 shows that the new folder also contains the file main.xml.


Figure 8. Downloading Data: The layout-land folder contains the UI to be displayed in landscape mode.
 
Figure 9. Emulate 3G: You can also apply the –land name extension to the drawable folder.

Basically, the main.xml file contained within the layout folder defines the UI for the activity in portrait mode while 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-land folder:

    

Basically, the main.xml file contained within the layout folder defines the UI for the activity in portrait mode while 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-land folder:

Using this method, when the orientation of the device changes, Android will automatically load the appropriate XML file.

Besides using the –land name extension on the res/layout folder, you can also use it on the res/drawable folder. For example, the res/drawable-land folder shown in Figure 9 contains images that are designed to be displayed in landscape mode while those in the res/drawable folder are designed to be displayed in portrait mode.

Persisting State Information During Configuration Change

One important concept you need to know when handling screen orientation changes is that when a screen changes orientation, the activity is destroyed and then recreated–the onCreate event is always called when the screen orientation changes. When this happens, the current state of the activity may be lost.

First and foremost, you should name all your views in your activity using the android:id attribute. This attribute is important for applications that change with screen orientation, because when Android destroys activities, it saves state only for named views. For example, suppose a user changes orientation in the midst of entering some text into an EditText view. When this happens, if you've named the EditText view using an android:id attribute, Android will persist any existing text inside the EditText view, and restore it automatically when the activity is recreated. In contrast, if the view does not have an android:id attribute, the system will not be able to persist the text, so when it recreates the activity, any already-entered text will be lost.

In addition, note that the onSaveInstanceState event is fired whenever an activity is about to be killed or put into the background. For example, when the orientation is changed, this event is fired so that the current state of the activity can be saved and restored later. This event is similar to the onPause event of the Activity class, but unlike the onPause event (which is called whenever the activity is being placed in the background or being killed), it is not fired when an activity is being unloaded from the stack (since there is no need to restore its state later on).

You can override the onSaveInstanceState event and save the information you need in this event. For example, 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 recreated, the onCreate event is fired first, followed by the onRestoreInstanceState event. This onRestoreInstanceState event allows you to retrieve the state that you have saved previously in the onSaveInstanceState event:

    @Override    public void onRestoreInstanceState(Bundle savedInstanceState)     {        super.onRestoreInstanceState(savedInstanceState);        //---retrieve the information persisted earlier---        String ID = savedInstanceState.getString("ID");       }

While you can use the onSaveInstanceState event to save state information, the limitation is 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. (Screen orientation changes are considered configuration changes, and by default, all configuration changes cause the current activity to be destroyed). You can save your current data structure by returning it in this event, like this:

    @Override      public Object onRetainNonConfigurationInstance()     {           return("Hello");       }

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);                String str = (String) getLastNonConfigurationInstance();    }

Bypassing the Activity Destruction Process

Suppose you don't want Android to go through the normal activity destroy-and-recreate process; instead, you want to handle recreating the views yourself. In this case, you can use the android:configChanges attributes on the element in AndroidManifest.xml:

                                                                        

The above attribute indicates that you want to handle the flipping of the keyboard and the changes in the accelerometer (sensor) yourself. When a change in orientation occurs, the onConfigurationChanged event will be fired, which you can override to redraw your activity:

    @Override     public void onConfigurationChanged(Configuration newConfig) {         super.onConfigurationChanged(newConfig);         //---code to redraw your activity here---        //...    }

Changing the Screen Orientation Based on the Accelerometer

If you want to change the screen orientation automatically based on the positioning of the device, you can use the android:screenOrientation attribute in the AndroidManifest.xml file:

                                                                                

The above specifies that the screen orientation for the Orientation activity is based on the sensor (accelerometer) of the device. If you hold the device upright, the screen will be displayed in portrait mode; if you hold it sideways, it will change to landscape mode.

Changing the Screen Orientation Programmatically

There are times where you need to ensure that your application is displayed only in a certain orientation. For example, suppose you are 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:

package net.learn2develop.OrientationAware;import android.app.Activity;import android.content.pm.ActivityInfo;import android.os.Bundle;public class Orientation 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 element in AndroidManifest.xml as follows to fix the activity to a certain orientation:

                                                                                

The above example fixes 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 changes.

Now, you've seen the various ways to handle screen orientation changes in Android and how an activity gets recreated when an activity changes orientation. You should be able to use these methods to persist your state during transition.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Related Posts