The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.
So far, we have been generally ignoring screen size. With the vast majority of Android devices being in a fairly narrow range of sizes (3” to just under 5”), ignoring size while learning is not a bad approach. However, when it comes time to create a production app, you are going to want to strongly consider how you are going to handle other sizes, mostly larger ones (e.g., tablets).
What you want is to be able to provide a high-quality user experience without breaking your development budget — time and money — in the process.
An app designed around a phone, by default, may look fairly lousy on a tablet. That is because Android is simply going to try to stretch your layouts and such to fill the available space. While that will work, technically, the results may be unpleasant, or at least ineffective. If we have the additional room, it would be nice to allow the user to do something with that room.
At the same time, though, you do not have an infinite amount of time to be dealing with all of this. After all, there are a variety of tablet sizes. While ~7” and ~10” screens are the most common, there are certainly others that are reasonably popular (e.g., Amazon’s Kindle Fire HD 8.9”).
Some apps will use the additional space of a large screen directly. For example, a painting app would use that space mostly to provide a larger drawing canvas upon which the user can attempt to become the next Rembrandt, Picasso, or Pollock. The app might elect to make more tools available directly on the screen as well, versus requiring some sort of pop-up to appear to allow the user to change brush styles, choose a different color, and so forth.
However, this can be a lot of work.
Some apps can make a simplifying assumption: the tablet UI is really a bunch of phone-sized layouts, stitched together. For example, if you take a 10” tablet in landscape, it is about the same size as two or three phones side-by-side. Hence, one could imagine taking the smarts out of a few activities and having them be adjacent to one another on a tablet, versus having to be visible only one at a time as they are on phones.
For example, consider the original edition of the Gmail app for Android.
On a phone, you would see conversations in a particular label on one screen:
Figure 324: Gmail, On a Galaxy Nexus, Showing Conversations
… and the list of labels on another screen:
Figure 325: Gmail, On a Galaxy Nexus, Showing Labels
… and the list of messages in some selected conversation in a third screen:
Figure 326: Gmail, On a Galaxy Nexus, Showing Messages
Whereas on a 7” tablet, you would see the list of labels and the conversations in a selected label at the same time:
Figure 327: Gmail, On a Galaxy Tab 2, Showing Labels and Conversations
On that 7” tablet, tapping on a specific conversation brings up the list of messages for that conversation in a new screen. But, on a 10” tablet, tapping on a specific conversation showed that conversation, plus the list of conversations, side-by-side:
Figure 328: Gmail, On a Motorola XOOM, Showing Conversations and Messages
Yet all of that was done with one app with very little redundant logic, by means of fragments.
The list-of-labels, list-of-conversations, and list-of-messages bits of the UI were implemented as fragments. On a smaller screen (e.g., a phone), each one is displayed by an individual activity. Yet, on a larger screen (e.g., a tablet), more than one fragment is displayed by a single activity. In fact — though it will not be apparent from the static screenshots — on the 10” tablet, the activity showed all three fragments, using animated effects to slide the list of labels off-screen and the list of conversations over to the left slot when the user taps on a conversation to show the messages.
The vision, therefore, is to organize your UI into fragments, then choose which fragments to show in which circumstances based on available screen space:
Figure 329: Tablets vs. Handsets (image courtesy of Android Open Source Project)
One solution is to say that you have the same fragments for all devices and all configurations, but that the sizing and positioning of those fragments varies. This is accomplished by using different layouts for the activity, ones that provide the sizing and positioning rules for the fragments.
So far, most of our fragment examples have been focused on activities with a single fragment, like you might use on smaller screens (e.g., phones). However, activities can most certainly have more than one fragment, though you will need to provide the “slots” into which to plug those fragments.
For example, you could have the following in
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/countries" android:layout_weight="30" android:layout_width="0px" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/details" android:layout_weight="70" android:layout_width="0px" android:layout_height="match_parent" /> </LinearLayout>
Here we have a horizontal
LinearLayout holding a pair of
Each of those
FrameLayout containers will be a slot to load in a fragment, using
getSupportFragmentManager().beginTransaction() .add(R.id.countries, someFragmentHere) .commit();
In principle, you could also have a
res/layout-h720dp/main.xml that holds both
of the same
FrameLayout containers, but just in a
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:id="@+id/countries" android:layout_weight="30" android:layout_height="0dp" android:layout_width="match_parent" /> <FrameLayout android:id="@+id/details" android:layout_weight="70" android:layout_height="0dp" android:layout_width="match_parent" /> </LinearLayout>
As the user rotates the device, the fragments will go in their appropriate slots.
However, for larger changes in screen size, you will probably need to have larger changes in your fragments. The most common pattern is to have fewer fragments on-screen for an activity on a smaller-screen device (e.g., one fragment at a time on a phone) and more fragments on-screen for an activity on a larger-screen device (e.g., two fragments at a time on a tablet).
So, for example, as the counterpart to the
shown in the previous section, you might have a
looks like this:
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/countries" android:layout_width="match_parent" android:layout_height="match_parent" />
This provides a single slot,
R.id.countries, for a fragment, one that fills
the screen. For a larger-screen device, held in landscape, you would use the
two-fragment layout; for anything else (e.g., tablet in portrait, or phone in
any orientation), you would use the one-fragment layout.
Of course, the content that belongs in the second fragment would have to show up somewhere.
Sometimes, when you add another fragment for a large screen, you only want it
to be there some of the time. For example, a digital book reader (like the one
we are building in the tutorials) might normally take up the full screen with
the reading fragment, but might display a sidebar fragment based upon an
action bar item click or the like. If you would like the BACK button to reverse
FragmentTransaction that added the second fragment — so pressing
BACK removes that fragment and returns you to the single-fragment setup — you
addToBackStack() as part of your
getSupportFragmentManager().beginTransaction() .addToBackStack(null) .replace(R.id.sidebar, f) .commit();
We will see this in the next tutorial.
So, what is the activity doing?
First, the activity is the one loading the overall layout, the one indicating
which fragments should be loaded (e.g., the samples shown above). The activity
is responsible for populating those “slots” with the appropriate fragments. It
can determine which fragments to create based on which slots exist, so it would
only try to create a fragment to go in
R.id.details if there actually is an
R.id.details slot to use.
Next, the activity is responsible for handling any events that are triggered
by UI work in a fragment (e.g., user clicking on a
ListView item), whose
results should impact other fragments (e.g., displaying details of the
ListView item). The activity knows which fragments exist at
the present time. So, the activity can either call some method on the second
fragment if it exists, or it can call
startActivity() to pass control to
another activity that will be responsible for the second fragment if it does
not exist in the current activity.
Finally, the activity is generally responsible for any model data that spans multiple fragments. Whether that model data is held in a “model fragment” (as outlined in the chapter on fragments) or somewhere else is up to you.
The preview of this section was eaten by a grue.
The preview of this section may contain nuts.
The preview of this section was whisked away by a shark-infested tornado.
The preview of this section is in the process of being translated from its native Klingon.
The preview of this section was whisked away by a shark-infested tornado.
The preview of this section is [REDACTED].