Chapter 11. Advanced Navigation

By now, you have just about all the major building blocks necessary to build a basic application. But sometimes, the basic building blocks just aren’t fun enough. This chapter gives you a basic overview of two of the most common navigational paradigms, the view pager and the navigation drawer.

The View Pager

A view pager is a way of navigating left and right through pages of data, generally using the swipe left and swipe right gestures. A view pager, like a list, takes an adapter, called a PagerAdapter, to generate the content to show. Two subclasses are built into the support library for the PagerAdapter: FragmentPagerAdapter and FragmentStatePagerAdapter. Both essentially accomplish the same thing—they show pages of fragments. Their nuances lie in how they handle non-visible fragments.

FragmentPagerAdapter holds onto in memory each fragment that the user visits, and it keeps the views immediately available for each fragment to the left and right of the visible fragment. This can be memory intensive, so it is more suited for a tab-like navigation where you have only three or four fragments in the adapter.

FragmentStatePagerAdapter holds onto only the savedInstanceState for each fragment visited. This means there will be more overhead when switching back and forth between fragments, but you should be able to page an indefinite number of fragments this way.

In the last chapter, I covered how to add tabs to a fragment; in this one, I’m going to expand on that example and make these tabs swipeable using the ViewPager.

Creating the Project

Instead of importing this project from sample code, we are going to use the new project wizard to generate the code, and I’ll go over the key parts of what these pieces actually mean.

In Android Studio, select File > New Project; in Eclipse, select File > New > Android Application Project. Give your new project a name, and set the API minimum to 11. Click Next until you get to the screen asking for the name of your activity and your layout. In the Navigation Type menu, select Fixed Tabs + Swipe (in Eclipse) or ActionBar Tabs with ViewPager (in Android Studio). Click Finish. A new project will be created that contains all the boilerplate code for using the view pager. If you run this code as it is, you should see three tabs (Figure 11.1).

Image

Figure 11.1 The view pager and action bar tabs from the generated code


Image Note

The wizard requires API 11 to generate the ViewPager code, but this is only because it uses the ActionBar. Based on what you learned in Chapter 10, you should have the tools necessary (using AppCompat) to convert this project to be compatible all the way down to version 8 if you need to.


You must be thinking, “This is great! The app is already halfway done!” Well, not quite, but this certainly does provide a great starting point for many projects. Let’s dissect the code in the activity and get an idea of what exactly is going on.

onCreate

The onCreate of this activity has a lot going on in it, but it is straightforward to follow. The generated code is heavily and helpfully commented, so let’s look over it. I’ve removed the comments for brevity, but the actual code should be similar.

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

   final ActionBar actionBar = getActionBar();
   actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

   mSectionsPagerAdapter = new SectionsPagerAdapter(
        getSupportFragmentManager());

   // Set up the ViewPager with the sections adapter.
   mViewPager = (ViewPager) findViewById(R.id.pager);
   mViewPager.setAdapter(mSectionsPagerAdapter);

   mViewPager.setOnPageChangeListener(new
      ViewPager.SimpleOnPageChangeListener() {
      @Override
      public void onPageSelected(int position) {
         actionBar.setSelectedNavigationItem(position);
      }
   });

   for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
      actionBar.addTab(actionBar.newTab()
         .setText(mSectionsPagerAdapter.getPageTitle(i))
         .setTabListener(this));
   }
}

Aside from the normal setup of the activity using setContentView, the first thing it does is set the ActionBar’s navigation mode to ActionBar.NAVIGATION_MODE_TABS. By specifying this, we are letting the ActionBar know that we want to show tabs, and that we want to be able to call tab-related functions. If this is not set, calling functions such as setSelectedNavigationIndex will throw an IllegalStateException, causing your application to crash.

Next, it creates a new class named SectionPagerAdapter. As we will see later, SectionPagerAdapter is a custom subclass of FragmentPagerAdapter, which is the class that is primarily responsible for providing fragments to page through.

After creating the adapter, it goes through the familiar process of finding the ViewPager, which is laid out in activity_main.xml, and then setting its adapter with the one we just created. It’s similar to the list view in that if you do not set an adapter onto it, no content will be shown.

The last piece of importance here is setting a page change listener. This is where we form a correlation between the action bar tabs and the view pager. This is saying, “when I change pages, change the selected tab to the new page.”

The code should look similar to the code in Chapter 10. It’s just a simple for-loop adding a number of tabs equal to the number of pages in the adapter.

The XML

The XML for this activity is simple. Actually, it feels too simple. It has only one item in it, and that is the ViewPager. Remember back in Chapter 3 when we created a view programmatically instead of by using XML? Since this layout has only one view, and its parameters are relatively simple, this might be one of those times that you want to create your layout in XML. Why don’t you give it a try?

<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/pager"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

As you can see, all that is being set here is a width, a height, and an ID. What might look different is that ViewPager is prefaced with android.support.v4.view. The ViewPager actually exists only in the support library, and not in the Android framework. Because of this, you have to reference it by its fully qualified name, just as if you were declaring one of your own custom views in XML.

FragmentPagerAdapter

The critical piece of the view pager lies within the adapter you supply to it. Creating a ViewPager subclass requires you to override two methods—getItem and getCount—and implement a constructor.

Image getItem should return the type of item to be displayed; in this case, a fragment.

Image getCount should return the number of items in the adapter. In the example code, it is returning 3, meaning three fragments will be paged through.

In the example code, only one type of fragment is used, but you can use any combination of fragments that you please.

public class SectionsPagerAdapter extends FragmentPagerAdapter {
   public SectionsPagerAdapter(FragmentManager fm) {
      super(fm);
   }

   @Override
   public Fragment getItem(int position) {
      Fragment fragment = new DummySectionFragment();
      Bundle args = new Bundle();
      args.putInt(DummySectionFragment.ARG_SECTION_NUMBER,
           position + 1);
      fragment.setArguments(args);
      return fragment;
   }

   @Override
   public int getCount() {
      // Show 3 total pages.
      return 3;
   }

   @Override
   public CharSequence getPageTitle(int position) {
      Locale l = Locale.getDefault();
      switch (position) {
      case 0:
         String title1 = getString(R.string.title_section1);
         return title1.toUpperCase(l);
      case 1:
         String title2 = getString(R.string.title_section2);
         return title2.toUpperCase(l);
      case 2:
         String title3 = getString(R.string.title_section3);
         return title3.toUpperCase(l);
      }
      return null;
   }
 }

The getItem function is creating a new fragment for the position that it’s passed. getItem is called internally by the FragmentPagerAdapter, and you do not need to call this yourself to show any of the fragments that you wish to display. The example is adding the position as an argument to the fragment so that it can use it to display its position in the fragment’s content, but this isn’t necessary if you don’t want to do it.

Finally, the example overrides a function named getPageTitle. While this isn’t required, it’s a nice helper function to associate a title with a position in the view pager. If you don’t override this, the default implementation will return null, so watch out for NullPointerExceptions.

DummyFragment

DummyFragment is basically just an empty fragment used to demonstrate that the pager holds fragments. Note that fragments can be inner classes and are not required to be a standalone class. This might prove useful for you if you have some fragments that aren’t too complex and perform very basic functionality. For example, if a fragment contains only a list and no other logic, it might make sense to have it as an inner class to the activity it’s used in, to provide more context into what its purpose is.

That’s it! The view pager is a great way to add top-level navigation to your application and add a fun and intuitive way for your users to discover content. As a rule, you should avoid using more than three tabs for your top-level navigation. If you have to go over three tabs, then it might be time to check out the navigation drawer.

The Navigation Drawer

The navigation drawer (Figure 11.2) is a UI pattern in which a drawer reveals itself from the left side of the screen, either because the user swiped from the left edge of the screen or because they pressed the home icon in the action bar. Users will know that a drawer is available by the presence of three lines next to the home icon. Some designers call this icon the “hamburger” because its three lines look like... well, you get it. Figure 11.3 shows what the standard three-lined hamburger icon looks like. You can customize this to any icon you’d like, but for convention, make sure it always has three lines.

Image

Figure 11.2 The navigation drawer

Image

Figure 11.3 The navigation drawer’s standard icon, sometimes known as the “hamburger”

The contents of the navigation drawer can be any view, but for our demo, we are going to use a list view. The code for this example is a modified example of the drawer demo code on the developer’s site, http://developer.android.com/training/implementing-navigation/nav-drawer.html. The official sample code is a great implementation, but for someone not familiar with all the Android APIs, it might be a bit overwhelming. I’ve removed all the extraneous pieces to demonstrate exactly what is necessary to get a drawer into your application.

onCreate

As with all Android projects, onCreate is the best place to figure out what’s going on. Let’s take a look. Since this is a little longer than the onCreate for the view pager, I’m going to break it into two parts.

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

   mTitle = mDrawerTitle = getTitle();
   mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);

   mDrawerList = (ListView) findViewById(R.id.left_drawer);
   ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, ITEMS);
   mDrawerList.setAdapter(adapter);
   mDrawerList.setOnItemClickListener(
      new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view,
           int position, long id) {
         selectItem(position);
      }
   });

   getSupportActionBar().setDisplayHomeAsUpEnabled(true);
   getSupportActionBar().setHomeButtonEnabled(true);
   ...

As usual, we are starting with setContentView to inflate the views for our activity (we will take a look at the XML soon). In our onCreate, we are referencing two views, a DrawerLayout and a ListView. A DrawerLayout is a subclass of ViewGroup, which means that its job is to hold other views. In this case, one of the views that it’s holding is the second one we are referencing, a ListView. This ListView is going to act as our navigation menu, and will be what the drawer will show when it is opened.


Image Tip

You can add a visible shadow to your navigation drawer by calling the setDrawerShadow method of the DrawerLayout with the drawable you’d like to use as a shadow. To use it, you would call the method like this (drawable image is included in the sample project): mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);


Just like we did in Chapter 5, we are going to create a simple adapter and set it to our list view. To add interactivity to the list view, let’s set an OnItemClickListener and give it a function to select an item. selectItem is a function we are defining ourselves. We’ll go into the contents of the select item after we finish onCreate.

Next, we add some flags on the action bar so that it knows what we intend to do with it. setDisplayHomeAsUpEnabled is telling the action bar to show a carrot next to the icon, indicating that pressing the icon will exhibit some sort of “up” or “back” behavior. setHomeButtonEnabled will simply make the home icon clickable.

   ...
   mDrawerToggle = new ActionBarDrawerToggle(
        this, mDrawerLayout, R.drawable.ic_drawer,
        R.string.drawer_open, R.string.drawer_close) {

     public void onDrawerClosed(View view) {
        getActionBar().setTitle(mTitle);
     }
     public void onDrawerOpened(View drawerView) {
        getActionBar().setTitle(mDrawerTitle);
     }
   };

   mDrawerLayout.setDrawerListener(mDrawerToggle);
   if (savedInstanceState == null) {
      selectItem(0);
   }
}

The last part of onCreate is where things start to get interesting. According to the Android documentation, the ActionBarDrawerToggle is “a handy way to tie together the functionality of DrawerLayout and the framework ActionBar to implement the recommended design for navigation drawers.”

The ActionBarToggleDrawer takes five arguments, the first three arguably being the most important. Here are the argument descriptions from the online documentation.

Image Activity. The activity hosting the drawer.

Image DrawerLayout. The drawer layout to link to the given activity’s action bar.

Image DrawerImageResource. A drawable to use as the drawer indicator (this is the hamburger icon mentioned earlier).

Image OpenDrawerContentDescription. A string resource to describe the “open drawer” action for accessibility.

Image CloseDrawerContentDescription. A string resource to describe the “close drawer” action for accessibility.

The creation of the ActionBarDrawerToggle is unique in that we are using anonymous inner functions to declare the behavior of the drawer when it is toggled. This is a stylistic choice, and you could very easily create a custom class that extends ActionBarDrawerToggle. I encourage you to use whichever you feel more comfortable with.

In both onDrawerClosed and onDrawerOpened in our example, we are adjusting the title in the action bar. This is not required, but it is a nice way to give your users a sense of where they are and what they are looking at. When the drawer is open, it should show the title of the drawer in the action bar; when the drawer is closed, it should reflect the content of what the user is looking at.

After initializing ActionBarDrawerToggle, we set it as the listener for our drawer layout. This will make sure that when the drawer toggle is selected, the drawer slides in and out. Now that we have all the pieces set up properly, we set the currently selected item to 0. You will notice that this is wrapped in an if statement that checks whether the saved instance state is null. If it is null, that means that this activity’s lifecycle is new. If it isn’t null, that means we have some sort of previous state, and we don’t want to reinitialize anything.

The XML

The drawer layout requires a specific setup to work properly. Since it is a child of a view group, its purpose is to hold other views. In a drawer layout, the first view is meant to be the content view, and the second view is meant to be the actual drawer. Setting layout_gravity on the second view is how you set the side of the screen the drawer emerges from. Setting layout_gravity to start will make the drawer emerge from the left side of the screen; setting it to end will make it emerge from the right.

<android.support.v4.widget.DrawerLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent">

   <FrameLayout
      android:id="@+id/content_frame"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />

   <ListView
      android:id="@+id/left_drawer"
      android:layout_width="240dp"
      android:layout_height="match_parent"
      android:layout_gravity="start"
      android:background="#FFF"
      android:divider="#BBB"
      android:dividerHeight="1dp" />

 </android.support.v4.widget.DrawerLayout>

Swapping Fragments

The navigation drawer is a great way to swap fragments around, and this is exactly what we’re doing in our app. The selectItem function that we mentioned earlier (and set to 0) is actually doing all the fragment switching for us.


Image Note

We are writing this function ourselves, which means that we can assign any arbitrary behavior to an item click from the navigation drawer.


private void selectItem(int position) {
   // update the main content by replacing fragments
   Fragment fragment = new DummySectionFragment();
   Bundle args = new Bundle();
   args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
   fragment.setArguments(args);

   FragmentManager fragmentManager = getFragmentManager();
   fragmentManager.beginTransaction().replace(
      R.id.content_frame, fragment).commit();

   // update selected item and title, then close the drawer
   setTitle(ITEMS[position]);
   mDrawerLayout.closeDrawer(mDrawerList);
}

If this code looks similar to code you’ve used previously to add fragments, that’s because it is! We are creating a new fragment and setting an argument on it, which is used for nothing more than visually displaying which fragment it is.

Next, we use the standard fragment procedure: Get the FragmentManager, begin a transaction, replace a fragment, and commit the change. Done! R.id.content_frame references the frame layout that is located in the activity’s XML (mentioned in the previous section).

At the end of these transactions, we are doing a couple of things. First, we are setting the title in the action bar to the item we selected. This will ensure that the title matches the content. After that, we close the drawer as soon as the user selects an item. The user appreciates any extra work you can do for them.

Near the end of the file, you’ll notice a couple of functions being overridden, onPostCreate and onConfigurationChanged. These are both making state synchronization calls onto ActionBarDrawerToggle. If you’re interested in what these calls are actually doing, a quick online search will lead you to the source code. In any case, just remember to add these calls into your activity, and your navigation drawer should behave properly.

Wrapping Up

Creating interfaces is one of the most enjoyable parts of Android development, and Android has provided some great tools that allow us to quickly create basic applications that use cutting-edge navigation patterns.

In this chapter, you learned how to use the new project wizard to build an application that already has an action bar with tabs and a view pager, and you learned about all the pieces that were generated.

You also checked out what the navigation drawer is all about. Although there is a lot you can do with a navigation drawer, there isn’t much code necessary to get it working.

From the basic view all the way up to the complex fragment, and from simple network requests to fully functional background services, you have all the tools you need to create just about any application you can imagine. Take some time to play around with all the examples we’ve created thus far, because in the next chapter, we’re going to upload one of these applications onto the Google Play Store so that anyone can download it! (Or no one, whichever you prefer.)

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

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