When I was asked if I could demo how to handle different screen sizes and rotation in Android, I created a small project which I’d like to share here. You can find the complete repo at Github.
The demo uses Xamarin.Android and was built using the Compatibility Lib v4 to support Android’s navigation drawer layout. Fragments are used directly and not via the compatibility packages.
The source in the repo is commented but I’m going to point out the challenges I had and the special parts.
The app’s idea is to show a list of countries. Selecting a country is going to display a famous soccer player (it was pretty hard to pick one for Germany! ;-)).
Supporting different screen sizes with layouts
There are various ways to support different screen sizes on Android. The most common one is for sure to use different XML layouts. This is what my demo does too. I first googled a bit on DuckDuckGo and found an interesting article about how to use the “smalls screen size” selectors. Back in the days there were screen size buckets (small, large, medium) which caused many issues especially on devices that would not clearly belong to one or the other device group. I wanted to support phone, small tablets and big tablets. So my solution contains layouts for
- layout-sw320dp (used for phone and if nothing else matches, for instance Samsung S3 or S5)
- layout-sw533dp (this matches my 1280×800, 240dpi Nexus 7)
- layout-sw800dp (big tablet like the Nexus 10)
The most notable part: the smallest width is independent from device rotation! Check out the article, it’s a good read.
You might wonder if there’s a way to figure out the smallest width? It’s an easy calculation:
sw = resolution in pixels * 160 / device DPI
For my Nexus 7: sw = 800 * 160 / 240 = 533
All of my layout folders contain two layout files:
- Main.axml: the start layout. For phones it contains a linear layout with a frame layout as a placeholder for the only fragment.
- Details.axml: this is view showing an image and a text label
Dealing with Fragments in the layouts
In order to be able to reuse as much code as possible I wanted to use Fragments and have only one Activity. For phones, the main layout contains only one FrameLayout inside a LinearLayout which acts as a placeholder for the CountryListFragment.
For smaller tablets I’m using a DrawerLayout. Here are important facts about DrawerLayout:
- It has to contain two inner elements.
- The first child must be details section.
- The second child must be the content of the drawer/the menu itself. The order is important to have the menu on top of the content!
- The drawer’s/menu’s layout gravity must be set (usually start or left)
Both of the children are in my case FrameLayouts acting as containers for the Fragments.
For big tablets I’m using a horizontal LinearLayout and place the two FrameLayout containers next to each other. In this case, a weight is provided to make the details pane twice as wide as the menu. Note: the weight can be anything you like! 2:1 is the same as 100:50 – only the relation is important.
The default transition of fragments is pretty poor. I added four animators to the project which can be found in Resources/animator. They all slide the screen by 100 units either left or right and adjust the alpha value.
Putting it all together in code
The entry point is MainActivity.cs. This tries to access the navigation drawer. If it finds one (remember: it will only be there for devices where the sw533dp layout applies) it will initialize it. To make that work OnPostCreate(), OnConfigurationChanged() and OnOptionsItemSelected() must be overridden too.
The next step is to set the main/menu fragment. This simply accesses Resource.Id.mainContainer – the nice thing is: this resource is the same for all layouts, so there is no need to check anything additional here.
Once the initial screen is done, action continues in CountryListFragment.cs. It creates some country information and populates the ListView. The important part is in OnListItemClick(). Here a new instance of the DetailFragment() gets created and then we have small check: for phones, we replace the contents of the main container. If we have a dedicated details container (for the tablets), this will be used instead. Basically the only thing in code to support multiple (very different!) layouts is this line:
int targetContainerId = this.Activity.FindViewById(Resource.Id.detailsContainer) != null ? Resource.Id.detailsContainer : Resource.Id.mainContainer;
Fragments versus Activities
Fragments can be hard to understand in the beginning, especially if you just got used to using Activities. In this demo I could have used a second activity for the details screen on phones. However this would have complicated the code and create more overhead. It’s so much simpler to just use FragmentManager and replace a fragment. However there are also some potential pitfalls:
- Fragment restarting: by default if the device is rotated the Activity and all Fragments will be restarted. Android will then call the Fragment’s default (parameterless) constructor. If you look at my code, you’ll notice there is no such constructor. Instead I only have a constructor taking an instance of a Country object. So why does it work anyway? I turned off automatic rotation handling by applying a ConfigurationChanges attribute to my main activity. This tells Android that I handle rotation myself. In this case it’s really easy because there is nothing to do! All layouts will simply adjust to the new width and height. If you’re layouts are completely different in landscape vs. portrait you will have to come up with alternatives.
- Replacing fragments: when I first implemented the layouts I did not have the FrameLayout containers in there. Instead I directly embedded fragments and created instances directly from XML using the class attribute. While this works, such fragments cannot be replaced! The fragment manager works on the containers holding the fragments, not the fragments themselves.
It was fun creating this demo and it also showed me how little my Android knowledge is compared to iOS 🙂