Chapter 10. Tablets, Fragments, and Action Bars, Oh My

So we’ve made it this far, and we’ve barely talked about fragments at all. Well, there’s reason behind this madness, I assure you. Fragments were added to the framework at a time when the Android platform was evolving to accommodate larger screens and needed a strategy for allowing developers to design one application to work across a myriad of devices.

Around the same time, the action bar was added to the framework. The action bar is a window feature that aims to provide a consistent navigation and action pattern for Android applications. Much like the menu bar across the top of all screens in OS X and the menu functionality tied to the top of each application window in Windows, the action bar serves as a universal (but styleable) design that users are accustomed to and have come to expect.

Fragments

Fragments, conceptually, are like activities with a slightly more complex lifecycle. They can be given a screen to themselves if there isn’t much room, or they can be placed with many other fragments on a larger tablet screen. An activity can contain any number of fragments. In this way, the Android SDK allows you to expand and collapse the views in your application to take advantage of more and less screen space. There is one thing the activity can do that the fragment cannot—the activity can register for intents in the manifest; fragments rely on their host activity to pass on launch information. Further, it’s important to implement fragments such that they are totally unaware of what other fragments are visible. This becomes important, because you’ll want to change that configuration depending on how much space you have.

If you’re planning on coding along with me in this chapter, make sure you have a project that is set to version 3.0 or higher of the Android SDK (API Level 11 or greater).

The Lifecycle of the Fragment

Fragments have fairly complex lifecycles. There are many methods to explore, but the onCreateView method must be implemented for the fragment to appear onscreen. onCreateView is your fragment’s one chance to create a view to display onscreen. If you fail to return a view to the system, your application will crash and burn.

Here is the startup lifecycle; the methods are listed in the order the system will call them:

Image onAttach is called when your fragment is attaching to an activity.

Image onCreate is called when the fragment is being initialized. This is a great place to initialize any variables you’ll need later.

Image onCreateView is your opportunity to create and return the fragment’s root view. This is the first method that will be called if your fragment is returning to the screen after having been previously paused.

Image onStart is similar to the same call on the activity; it is called when the fragment is about to be placed onscreen.

Image onResume is called when the fragment is back onscreen.

At this point, your fragment is frolicking on the screen, receiving touch and key events, or just hanging around and looking great. If the user leaves the screen or switches to a view that no longer includes the fragment, the following shutdown lifecycle will occur:

Image onPause is called if the fragment is removed from the screen or the user presses the home button. This is the only part of the shutdown lifecycle you’re guaranteed to get (it would be the only method you get in the rare situation that your application is put in the background and then your process is killed due to resource constraints). onPause is the best time for you to save any data or state information that you want the user to be able to see when the fragment is resumed later.

Image onStop is similar to the activity’s version of this method; it is called when your fragment has left the screen. It tends to be called in conjunction with the activity’s onStop method.

Image onDestroyView is your last chance to pull data out of the views before they go away.

Image onDestroy is called when the fragment is being removed from the screen and will not return. This is the time to make sure all your threads are stopped, loaders are canceled, and any broadcast receivers are unregistered.

Image onDetach is called as the fragment loses its association with an activity; it is the last of your methods the system will call before the fragment heads to the great garbage collector in the sky.

Creating a Fragment

To create a fragment, you’ll need to create a Java class that extends the Fragment class. An incredibly simple implementation would look something like this:

public class ContentFragment extends Fragment{
   @Override
   public View onCreateView(LayoutInflater inflater,
      ViewGroup container, Bundle savedInstanceState) {
      View root = inflater.inflate(R.layout.fragment_content,
         container, false);
      ...
      return root;
   }
}

Fragments, of course, need their own layouts to show anything onscreen. The ContentFragment class in the sample implementation will show a simple text view. Here is the fragment_content.xml file whose contents will be drawn as the fragment itself:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <TextView
      android:id="@+id/header_text"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/hello_world" />

   <TextView
      android:id="@+id/content_text "
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/lorem_ipsum" />

</RelativeLayout>

With the XML layout file and the new ContentFragment class, you’ll have a very basic but functional fragment for displaying text on the screen. Just as with using an activity, you can call findViewById on the root view from onCreateView:

TextView mContentTextView;
@Override
public View onCreateView(LayoutInflater inflater,
   ViewGroup container, Bundle savedInstanceState) {
   View root = inflater.inflate(R.layout.fragment_content,
      container, false);
   TextView tv =
      (TextView) root.findViewById (R.id.header_text);
   tv.setText("Checking out fragments!");
   mContentTextView = (TextView) root.findViewById(R.id.content_text);
   return root;
}

If you need to access the root view outside of onCreateView, you can safely save it in a member variable, or you can call the getView method, which will return the view that was returned from onCreateView. Here’s what a method accessing getView might look like:

private void setContentText(String text){
   TextView tv =
      (TextView) getView().findViewById (R.id.content_text);
   tv.setText(text);
}

Keep in mind, however, that the getView method works only after you’ve returned from onCreateView. While you now have a fully functioning fragment, you still need to make it appear onscreen.

Showing a Fragment

There are two main ways you can make fragments appear onscreen.

Using XML

You can declare a fragment in an XML layout to make the fragment appear onscreen, like so:

File: res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <fragment
      android:id="@+id/list_fragment"
      android:name="com.peachpit.fragmentdemo.ContentFragment"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
</LinearLayout>

This layout can then be set as the content view for a FragmentActivity, just like any other layout file:

public class ContentViewingActivity extends FragmentActivity{
   public void onCreate(Bundle data){
      super.onCreate(data);
      setContentView(R.layout.activity_main);
   }
}

Figure 10.1 shows the results of using XML to make the fragment appear onscreen.

Image

Figure 10.1 A fragment with a single text view

Fragments, when set up this way, can be placed onscreen the same way as views, which makes it easy to include many of them in a single layout file. So if one fragment is awesome, two fragments must be even more awesome! Let’s create another fragment, and call it DemoListFragment.

public class DemoListFragment extends ListFragment {
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      ArrayList<String> data = new ArrayList<String>();
      for(int i = 0; i < 20; i++){
         data.add("Item " + i);
      }
      ArrayAdapter<String> adapter =
         new ArrayAdapter<String>(getActivity(),
            android.R.layout.simple_list_item_1, data);
      setListAdapter(adapter);
   }
}

A list fragment is very similar to the ListActivity we covered in Chapter 5, except that it’s a fragment. It has a ListView built into it, and if you want to override the default list in it, you just inflate a layout file that contains a ListView that has an ID of android.R.id.list. But since we aren’t inflating a custom layout here, it’s OK to do all the fragment setup logic in onCreate.

Now that we have two fragments, let’s set this list fragment to show only while the device is in landscape. In your project, create a new folder in the res/ folder and name it layout-land. Into that new folder, put a new XML file named activity_main.xml (exactly the same as in the res/layout folder). As we know from Chapter 7, when the device is in landscape, it will choose this file instead of the default one. Go ahead and add this XML to the layout (it shows a ListFragment with the ContentFragment next to it):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal" >
   <fragment
      android:id="@+id/list_fragment"
      android:name="com.peachpit.fragments.DemoListFragment"
      android:layout_width="0dp"
      android:layout_height="match_parent"
      android:layout_weight="1"/>
   <fragment
      android:id="@+id/content_fragment"
      android:name="com. peachpit.fragments.ContentFragment"
      android:layout_width="0dp"
      android:layout_height="match_parent"
      android:layout_weight="3"/>
</LinearLayout>

I’ve used a linear layout and some weighting in the fragments to give the DemoListFragment the left one-third of the screen and the ContentFragment the right two-thirds. (If you’re wondering about DemoListFragment, you can find it in the sample code for this chapter.)

Figure 10.2 shows what it looks like on a device in landscape mode.

Image

Figure 10.2 Two fragments on one screen

Using the fragment manager

Although being able to lay out fragments in the XML files is great, you’ll want to be able to interact with the fragments on your screen at runtime as well. For this, you’ll use the FragmentManager, which is Android’s tool for manipulating fragments.

Getting a fragment manager on Honeycomb and later requires you to call getFragmentManager. Getting a fragment manager for earlier devices requires you to call getSupportFragmentManager from a FragmentActivity.

You can add fragments to the screen programmatically with the following code:

FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.add(containerViewId, new DemoListFragment());
ft.commit();

The variable containerViewId should refer to an existing ViewGroup in your activity’s layout where the new fragment should be placed. You can also, at a later time, replace one fragment with another by calling replace(containerViewId, newFragment);, where containerViewId specifies the view container that currently holds the fragment you’d like to replace. You can replace only fragments that were previously added using a FragmentManager transaction; fragments declared statically in XML layouts cannot be replaced.

By using either XML or the fragment manager to set up and modify your fragments, you should have no trouble building complex, scalable, and beautiful applications that render well on both handsets and tablets.

Remember that all fragments should work independently of their siblings (in much the same way that activities should stay independent), even if they might share the same screen.

Given the power of Android’s layout folders (which we covered at length in Chapters 3 and 7), you should see the possibilities in building one layout for tablets (which could have several fragments on it) and building another for small-screened phones (which would have only one visible fragment at a time).

If you’re looking for a simple example, I highly recommend you take a look at the project in the sample code for this chapter.

Providing Backward Compatibility

Android has, thankfully, bundled fragments into its compatibility library. This means you can work with fragments even if you’re planning on supporting pre-3.0 devices. I highly recommend that you use it whenever you can. In Eclipse, installing it is as simple as selecting the menu item shown in Figure 10.3.

Image

Figure 10.3 Installing the compatibility library

In Android studio, all you have to do is add the following line to the second dependencies section of your Gradle file (the one that doesn’t have classpath 'com.android.tools.build:gradle:0.X.+' in it). If there isn’t a second dependencies section, go ahead and add it.

dependencies {
   compile 'com.android.support:support-v4:18.0.0'
}

Take note, however, that building your project against a 3.0 or higher version and still using the compatibility libraries at the same time can get a little complicated. If you’re doing this, make sure all your imports come from the support library like this:

import android.support.v4.app.Fragment;

instead of like this:

import android.app.Fragment;

Using the support library will ensure that your application will run correctly on newer and older systems alike.

Further, if you’re planning on using the compatibility library and fragments, remember that you’ll need to use a FragmentActivity instead of a regular activity; or, as you will see in the next section, you can also use an ActionBarActivity.

With the compatibility support and the dynamic nature of fragments, it becomes quite possible to create an application with a great interaction model that works well on both phones and tablets. I don’t have the space to spell it all out here, but there is sample code for achieving this in the companion source code for this chapter. Remember what you’ve read here, and take a look through it.

The Action Bar

With the transition from Android 2.3 to 3.0, Google eliminated both the search button and the menu key. From personal experience, I can tell you that many new users never find functionality that is placed in the options menu. Its removal from the system is, indeed, a very good thing.

Google moved the icons that used to reside in the options menu to the action bar. Further, you can specify a search view in the action bar (to replace the search button). This takes up more screen space, but on a tablet (and on later phones), there is more than enough space to go around.

The action bar now represents the primary way your users will navigate through your applications. Sadly, this tool is available only to versions 3.0 (target 11) or later, but it is available to earlier versions if you use the AppCompat library. Since there is a decent chance you will want to know how to get this support action bar working, and since the APIs for the support action bar and the actual action bar are nearly identical, I’ll quickly show you how in both Eclipse and Android Studio before going on.

Setting Up the AppCompat library

If you really aren’t interested in supporting Android versions prior to 3.0, set your project’s target Android version to 11 and skip ahead to the section “Adding Elements to the Action Bar.” Those of you who are choosing to support all devices, carry on.

Android Studio wins the award for ease of setup with the AppCompat library. In your project’s Gradle file, add this highlighted line in the same place that the support library is located:

dependencies {
   compile 'com.android.support:support-v4:18.0.0'
   compile 'com.android.support:appcompat-v7:18.0.0'
}

All done! Just select Tools > Android > Sync Project With Gradle Files to have Android Studio pull this library into your project.

On Eclipse, it will take a little more elbow grease, but it’s still pretty easy. The first thing you need to verify is that you have downloaded the support repository. This is located in the SDK Manager (Window > Android SDK Manager). At the bottom of the list, look for the Android Support Repository, and download it if you haven’t already.

Now, with your SDK ready, you need to import the AppCompat project as an Android library. This is almost identical to how you have been importing the sample code for this book into Eclipse. Select File > Import, and then locate where you have Eclipse ADT installed. Within the installation folder, choose sdk > extras > android > support > v7 > appcompat, and click OK (Figure 10.4). If you don’t see the appcompat file there, head back to the SDK Manager and download the support repository again.

Image

Figure 10.4 Locate the AppCompat library in your SDK.

The AppCompat library is now in your Eclipse workspace, which allows you to add this project onto other projects to provide its functionality. Now create a new project, and leave the minimum SDK at 8 or 9 (if you’ve downloaded them; otherwise, just set it to the lowest version you have). With your new project created, we are going to add the AppCompat library to it.

Right-click your new project folder in Eclipse, and select Properties from the bottom of the menu. From the left menu in the Properties dialog, select Android. On the right, below the Project Build Target section, is the section where you can add Android libraries. Click Add, and a dialog with a list of possible Android libraries in your workspace will appear. Select android-support-v7-compat, and click OK (Figure 10.5).

Image

Figure 10.5 Adding appcompat as a library project

Now look at the far left menu again, and select Java Build Path. On the right, you should see four tabs. Select the third tab, Libraries, and then click Add JARs. Navigate into the AppCompat library project and look in the libs folder for the file appcompat.jar (Figure 10.6). Select it, and click OK. Lastly, select the Order and Export tab, and select the appcompat.jar file (Figure 10.7). You’re all set! Now click OK to apply the changes and return to the project.

Image

Figure 10.6 Adding appcompat.jar to your project’s build path

Image

Figure 10.7 Enable appcompat in the Order and Export tab.

Assuming everything worked as planned, select Project > Clean > Clean All Projects, and click OK to force Eclipse to rebuild the workspace and make sure that your libraries are playing nicely with each other.

Showing the Action Bar

Now there are only two pieces left to showing the action bar. The first is straightforward. In the MainActivity that was created with your project, change extends Activity to extends ActionBarActivity. This might require you to organize (Eclipse) or optimize (Android Studio) imports to get your IDE to recognize the new class. The app should compile, and it looks like you’re on your way to glory. But if you run this code, you will be prompted with an error. Look in the logs, and you’ll see this error:

java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

Often, logs can be meaningless or hard to decipher. This, however, is not one of those times. As it suggests, we need to go into our styles.xml file and modify the theme for our application. Open styles.xml (res/values/styles.xml) and change

<style name="AppBaseTheme" parent="android:Theme.Light">

to

<style name="AppBaseTheme" parent="Theme.AppCompat.Light">

While we’re at it, we also should modify the styles.xml in all the other resource folders. The app creation wizard usually creates a similar styles.xml file in res/values-v11 and res/values-v14. Apply this same change to those styles, and try running your application again. If you’re running your application on an emulator that’s 4.0 and above, congratulations, it looks exactly like all the other examples in this book! Don’t roll your eyes so quickly—the power of AppCompat is that now, if you run this application on a device running Android 2.2 Froyo or Android 2.3 Gingerbread, it will look nearly identical. Now that’s awesome.

Adding Elements to the Action Bar

Now that we have our action bar set up and running for all desired versions, let’s get into the fun stuff. In addition to allowing you to set your own title with a call to setTitle, the action bar can handle three different types of objects:

Image Menu items, both in a drop-down menu on the right side and as actionable items in the bar itself.

Image Tabs. Buttons along the top, built to manipulate fragments on the screen.

Image Action views. Search boxes and drop-down lists (for things like sort orders, account selection, or death-ray intensities).

Further, Android will always make your application icon (farthest to the left) clickable. It is expected that tapping this icon will, by default, return the user to your application’s home screen (whatever this means to your application’s behavior). You can also visually indicate that the home icon will go one level back in the activity stack by calling setDisplayHomeAsUpEnabled(true).

The way you make changes to what’s in the action bar is very similar to how you used to interact with the options menu. This is not by accident. Because the action bar is supposed to replace the options menu, you call methods and configure files similarly to how you used to deal with the menu. This also makes it easy to gracefully degrade service to phones on older versions of the Android SDK.

Adding a menu item

Action bar menu items are actionable items that are visible on the right side of your action bar (Figure 10.8). They are placed there by overriding onCreateOptionsMenu in your activity and adding the menu items you’d like. By default, the new project wizard generates the code that overrides this function for you. Let’s take a look into the menu.xml file that’s being inflated and try to add our own menu item.

Image

Figure 10.8 Adding an action bar menu item

Here’s the code, located in your ActionBarActivity (or Activity), that is inflating the menu into the action bar:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
   // Inflate the menu; this adds items to the ActionBar if it is present.
   getMenuInflater().inflate(R.menu.main, menu);
   return true;
}

And here is what the XML code for a basic action bar menu item looks like:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto">
   <item
      android:id="@+id/action_add_item"
      android:title="@string/action_add_item"
      android:icon="@drawable/ic_menu_add"
      android:orderInCategory="100"
      android:showAsAction="always"
      app:showAsAction="always" />
</menu>

Let’s dissect what’s going on here. The first block is fairly unambiguous. We are getting the menu inflater, and inflating the layout file referenced by R.menu.main. All menu XML files should be located in the res/menu/ folder, kept separate from your other layout files.

The XML file is a little more interesting. The first line should seem familiar. This is the ID of the menu item. Just like a regular view, this menu item will be referenced by the ID given in this file. The title is a multipurpose piece of text that is used by the system in a number of ways. If the item is just text, you will see this title. If the menu item gets pushed into the action overflow, you will see this title there. Finally, if you have the item showing only the icon and no text, the user will be shown this text in a pop-up when long-pressing the button, letting them know what this is. orderInCategory sets the importance of this menu item, such that it will be shown earlier or later in the ordering depending on its value. In this case, the lower the number, the higher the priority.

The last and possibly most interesting piece here is the showAsAction attribute. We have it set to always, but you can change it to ifRoom, withText, never, or any combination of them by adding the pipe (|) character. For example, if you want to show a menu item always and with the text, you would set it to always|withText. The next interesting thing about this menu item is that we are duplicating showAsAction with the app prefix instead of the Android prefix. Because of how the AppCompat action bar is implemented, it is necessary to add this twice: once for the default action bar implementation, and once for the AppCompat implementation. It isn’t the most ideal scenario, but it’s still a far more practical solution than trying to implement a custom menu solution yourself.


Image Tip

When trying to figure out what arguments an XML item can take, press Control+spacebar to get a drop-down list of all possible arguments for that item.


Reacting to Menu Item clicks

When the user clicks one of your action bar menu items, Android will call your implementation of the onOptionsItemSelected method. Here’s my very simple example method:

public boolean onOptionsItemSelected(MenuItem item){
   int id = item.getItemId();
   if(id == android.R.id.home){
      // The user clicked the left-hand app icon.
      // As this is the home screen, ignore it.
      return true;
   } else if (id == R.id.action_settings) {
      // Launch the settings
      return true;
   }
   // Return false if you didn't handle this id
   return false;
}

With that, you should have the basics of creating both options menu and action bar menu items. If you desire, you can also create menu options dynamically by adding them in the onCreateOptionsMenu. While it’s not as elegant, sometimes it’s the only way to get the job done.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
   int groupId = -1; // No group for this one
   int itemId = 123456; // TODO Generate a better ID somewhere
   int order = 100;
   menu.add(groupId, itemId, order, "Settings");
   return true;
}

Adding a tab

Placing a tab in the action bar is a totally different process. You’ll use the ActionBar class itself to add a tab. Further, you’ll need to implement an ActionBar.TabListener onto your activity (it tells the system, through a series of overridden methods, what to do when the tab is tapped by the user).

Once you’ve implemented your listener, you should then add the following code to your activity’s onCreate method:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);

    ActionBar bar = getSupportActionBar();
   // Use getActionBar() if not using AppCompat lib

    bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
    ActionBar.Tab tab1 = bar.newTab();
    tab1.setText("Tab Text");
    tab1.setTabListener(new ExampleTabListener());
    bar.addTab(tab1);
}

For each tab you’d like to add, you must go through the process of requesting a new tab from the action bar, setting the listener, and then adding it to the action bar. Figure 10.9 shows what the fragment app would look like with tabs for navigating through different content.

Image

Figure 10.9 Adding tabs under the action bar

Each tab can trigger different events on the fragments within an activity. In the demo example, selecting another tab will switch to a different content fragment.

Using action views

Action views (like search and menu drop-downs) are somewhat complex and thus are impossible to go into in great detail here. You can use them to add search fields (as there is no longer a hard search button in Honeycomb and beyond) as well as drop-down menus. Figure 10.10 shows a calendar picker implemented as a drop-down menu on a phone running Ice Cream Sandwich.

Image

Figure 10.10 A drop-down list action view

For more information on how to make drop-down lists, search fields, and even your own custom views, check out the action bar documentation, at http://developer.android.com/guide/topics/ui/actionbar.html.

Wrapping Up

As you can see, Google has built user interface paradigms that can be used across all devices. Using the action bar, Google was able to do away with two physical menu buttons (options menu and search), while keeping those concepts active in the user experience. The options menu button was replaced by menu items added to the action bar, and the search button was replaced by the ability to add custom action views to that very same bar.

With fragments, Google has enabled us to place more or less on the screen as the available real estate shifts between devices. Through fragments, we are no longer limited to having one thing on the screen at any given time. However, if we need to handle smaller screens, fragments make it easy to shift back into the one-thing-per-screen layout.

Now that you have an understanding of fragments and the action bar, in the next chapter we’ll take a look at some useful navigation paradigms that will help you build intuitive applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset