For today’s entry in 31 Days of Android we’re going to talk about device orientation. In the easiest terms, orientation dictates if the device is horizontal (landscape) or vertical (portrait). The orientation of a device can affect many different things. Most commonly, you might have different layouts depending on the orientation. Less commonly, you might want to prevent a layout from displaying in a specific orientation or show an animation specific to that orientation. We’re going to start our work using the code from Day 10.
Detecting the Current Orientation
There are two different ways to detect the current device orientation. There is a way you can use if you’re programming for devices prior to 2.2 and a way you can use for 2.2 and above. The former method is now deprecated so if you’re not supporting Android below 2.2, I wouldn’t bother with it.
Open up your DayTenActivity and after setting your lblTextViewOne you can get an instance of display and then use the getOrientation method. You can then check it’s orientation against some Configuration constants. Unfortunately this method also requires us to compare the height and the width of the screen. Even more unfortunate, you’ll see that the orientation reported as ORIENTATION_PORTRAIT is actually landscape:
lblTextViewOne = (TextView) findViewById(R.id.lblTextViewOne);
lblTextViewOne.setText(R.string.test_one);
Display display = ((WindowManager) this.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
if (display.getOrientation() == Configuration.ORIENTATION_UNDEFINED) {
if (display.getWidth() == display.getHeight())
lblTextViewOne.setText("Square");
else if (display.getHeight() > display.getWidth())
lblTextViewOne.setText("Portrait");
else
lblTextViewOne.setText("Landscape");
}
else if (display.getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
lblTextViewOne.setText("Not Portrait, Landscape!");
}
When you first launch your application, the TextView at the top will say “Portrait” because the orientation matches Configuration.ORIENTATION_UNDEFINED and the height is greater than the width. If you rotate the emulator (Ctrl+F12), the text will change to “Not Portrait, Landscape!”. As you can see from the code, the orientation isn’t matching up with a landscape constant, but Configuration.ORIENTATION_PORTRAIT instead. This doesn’t make a lot of sense though it does work. The 2.2 and above method makes much more sense. Again you’ll use the Display object but this time you will call getRotation on it. This you can then compare against Surface.ROTATION_X constants:
switch (display.getRotation()) {
case Surface.ROTATION_0:
lblTextViewOne.setText("Rotation 0");
break;
case Surface.ROTATION_180:
lblTextViewOne.setText("Rotation 180");
break;
case Surface.ROTATION_270:
lblTextViewOne.setText("Rotation 270");
break;
case Surface.ROTATION_90:
lblTextViewOne.setText("Rotation 90");
break;
}
When you do this and run the application you’ll see that in portrait, getRotation will equal Surface.ROTATION_0. When you rotate the display (Ctrl+F12) you’ll see Surface.ROTATION_90. You can safely unwisely (see the comments below) assume that ROTATION_0 and ROTATION_180 are portrait and ROTATION_90 and ROTATION_270 are landscape.
Before we continue, it’s worth pointing out why the text in our TextView changes when you rotate the device. The code that you added to set the text depending on orientation was in the onCreate method. This means that whenever you rotate the device, your activity’s onCreate method will be called. This may be a little confusing since it may not make sense to recreate the activity when it’s already opened and you’re just rotating. This has to do with the Activity LifeCycle which we’ll get into another day. For now, just remember that rotating will cause your Activity to be recreated.
Getting Triggers for Device Orientation Change
There are a couple methods to detect an orientation change. The first method is to override the onConfigurationChanged method of the activity. Go ahead and do that for DayTenActivity. Passed into that method is a new Configuration variable which contains an orientation which you can check for being landscape or portrait:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
lblTextViewOne.setText("onConfigChanged - Landscape");
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
lblTextViewOne.setText("onConfigChanged - Portrait");
}
}
If you run your app now, this method won’t be called. In order for Android to know that the onConfigurationChanged method should be called you have to specify it in the manifest file. Open up your manifest and add a configChanges attribute to your DayTenActivity like so:
<activity
android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden"
android:name=".DayTenActivity" >
Now when you run your emulator and rotate, the onConfigurationChanged method is called. However, the onCreate is no longer called though the view is rotated. Interestingly, if you remove |keyboardHidden from the manifest, then the onConfigurationChanged is only fired when you rotate from landscape to portrait, not when you go from portrait to landscape (at least in the default emulator).
The second way you can listen for orientation changes is by implementing an OrientationEventListener in your activity. First you need to add an OrientationEventListener as a private member variable of your activity:
private OrientationEventListener MyOrientationEventListener;
Then, in your onCreate method you need to set up your listener and then make sure it can work:
MyOrientationEventListener = new OrientationEventListener(this,
SensorManager.SENSOR_DELAY_NORMAL) {
@Override
public void onOrientationChanged(int arg0) {
lblTextViewOne.setText("Orientation: " + String.valueOf(arg0));
}
};
if (MyOrientationEventListener.canDetectOrientation()) {
MyOrientationEventListener.enable();
} else {
lblTextViewOne.setText("Can't DetectOrientation");
finish();
}
First, you instantiate the listener. Note that you’re passing in SENSOR_DELAY_NORMAL here. You can set different constant values here which will dictate how often the sensor will detect changes. If you’re creating a fast action game, you’ll probably want something more intense than the normal setting. In the listener’s onOrientationChanged method, you’re just setting the text to be the degrees of orientation (remember that 0 and 180 are portrait and 90 and 270 are landscape). This method isn’t only called when the orientation rotates so while you’ll only see the matching orientation numbers with the emulator, if you run this on a real device, you will see it go through the full range of degrees as you rotate the device. Note that at the bottom you’re starting the listener if you can detect orientation and you’re finishing the activity if you can’t. You’d probably want to handle things a little more gracefully if for some reason the sensor wouldn’t work. One example of an app that might use this is the possibly infamous iBeer app that graphically shows a beer pouring as you tilt the device.
Specifying Layouts for Orientations
Creating different layouts for an orientation is relatively easy and we already spoke about it a little bit. To create an orientation for a specific layout, right click on your res folder and go to New -> Other and then choose Android XML Layout File. Let’s create orientation specific layouts for our second_layout. If you enter “second_layout” into the File field, the wizard will warn you that the destination file already exists. You can ignore this for now and click Next. On the next screen you can specify qualifiers to limit when the layout you’re creating is used. Find Orientation in the left list and click the right arrow. A new drop down will appear on the right side. Choose landscape from that menu and you’ll see that the folder at the bottom will change to /res/layout-land. Click Finish to create the layout. Now repeat that but choose portrait to create a layout in /res/layout-port.
If you run your app now and tap the second button which fires off the second activity, the app will crash. The reason it crashes is because in the ActivityTwo code sets the text value of textView1. Since you haven’t added the TextView to your new layouts, this won’t work. To fix this, copy the TextView from our original second_layout.xml to the new ones:
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
Now you can alter the two new second_layout’s to make them look better depending on the orientation.
Preventing a Layout from Rotating
The last orientation specific thing we’ll talk about is how we prevent an activity from rotating. In order to do so you can set a requested orientation in the onCreate method like so:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Adding this to DayTenActivity will prevent it from being shown in portrait. After adding this and running your app, when you rotate, you’ll always see landscape. Conversely, if you pass in SCREEN_ORIENTATION_PORTRAIT you’ll always get portrait. Another method of accomplishing the same thing is to specify screenOrientation in your manifest file as an attribute on your activity. If you wanted to lock DayTenActivity to landscape, you would do so like this:
<activity
android:label="@string/app_name"
android:screenOrientation="landscape"
android:name=".DayTenActivity" >
Remember that while you can lock screen orientation easily now, think about your users and what they would expect your app to do. Most people expect that when they rotate their device, the view will change to reflect having more room vertically or horizontally. If you lock the orientation, that expectation is not met.
You can download today’s code here.