Monday, August 8, 2011

Orientation for both phones and tablets

Tablets are now a new component to take into account when programming applications on Android.
And it brings issues with your application orientation...

My game Word Prospector was meant to be played in portrait mode on a phone.
But on tablets, I want it to be played in landscape ( as it feels more natural for tablets ).

But how to achieve that ?

Activity orientation is fixed in the manifest, and you can't have different manifests for different screen configurations.

So how to manage that ?
I found three different ways...

First solution : different apk for different screen configurations

Now that the Android market gives us support for multiple apk, ( see here ), you can use it just to have some differences in the manifest.
So you can have the very same application and only have different orientations in the manifest, and let the market make the work for you.

But having several apk is painful for developers : it's a lot of work to develop, and to maintain.
So I would  not recommend this solution.

Second Solution : have some activity code to manage the orientation

You can impose the orientation of your activity in the onCreate method of the activity, with the setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_XXX ) function.

So you can have some code like this :
if ( isXLargeScreen() )
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
else
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

With this code, when there is an orientation change, the activity is destroyed, then re created.
To avoid this destruction / recreation step, that would break the smoothness of the user experience, you have to disconnect the destruction of the activity mechanism.
So you have to add a
android:configChanges="keyboardHidden|orientation"
parameter in the activity declaration in the manifest, and add an empty onConfigurationChanged method.

Now you can even be more flexible.
If you want one configuration ( for instance the tablet one ) to handle both orientation, and the other ( the smartphone ) to handle only one orientation, you can do it :

In the OnCreate method, impose the orientation only for the smartphone :
if ( ! isXLargeScreen() )
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

Then in the onConfigurationChanged, you have to recreate the Activity only when on tablet. And in this case, you have to save the activty state, and restore it, as the classic destruction / creation of the activity is deactivated :

    public void onConfigurationChanged (Configuration newConfig)
    {
        //Log.d("tag", "config changed");
        super.onConfigurationChanged(newConfig);

        if ( ! isXLargeScreen() )
            return;
      
        // CODE FOR ROTATION
        // FOR BIGSCREENS :
        SaveActivityState();
        setContentView( R.layout.MyActivity );
        RestoreActivityState();
    }

Third solution : let's get back to the manifest...

The manifest really is the tool Android developers gave us to handle the orientation...
So my last ( and prefered ) solution is to have two different activities, with different orientation capacities :

        <activity     android:name="LetterGame"
                    android:screenOrientation="portrait"
                    android:configChanges="keyboardHidden|orientation"
                      android:label="@string/app_name">
        </activity>
        <activity     android:name="LetterGameLandscape"
                    android:configChanges="keyboardHidden|orientation"
                    android:screenOrientation="landscape"
                      android:label="@string/app_name">
        </activity>


and to create a MyActivityLandscape class, that just do ... nothing !
It just inherits from MyActivity :

package com.alocaly.LetterGame;

public class MyActivityLandscape extends MyActivity
{
}

Then you just have to choose what activity to launch in the caller activity :

Intent i;
if ( isXLargeScreen() )
    i = new Intent(MainMenu.this, MyActivityLandscape.class);
else
    i = new Intent(MainMenu.this, MyActivity.class);
startActivity( i );

And that's it !!
The onConfigurationChanged function should still be empty, or you can specialize it for your second activity.

Do you see another technique to handle different orientations with different devices ?


And my turn to ask a question :
How can we easily implement an isXLargeScreen method ? ( my current implementation is something I'm really proud of :) )