Android App Development

How to move activity content and action bar when closing and opening a navigation drawer

By Mike Woods, Atimi Software Inc.

Like me, you’ve probably searched online for a navigation drawer solution that doesn’t require a third-party library. When developing apps, we’re always wary about adding extra dependencies, and in this case, the libraries available don’t even mimic all navigation drawer features.

This tutorial shows you how to modify the navigation drawer so it moves the window’s content when it opens and closes. We will modify Google’s sample app with around 10 lines of code to add this functionality.


Implementation

Begin by loading the sample app. We’ll add code directly to these files to illustrate the technique. http://developer.android.com/training/implementing-navigation/nav-drawer.html
Since the action bar will be moving over when the drawer opens and closes, the drawer needs to claim the area under the action bar. This is achieved by making the action bar an overlay so the window lays out the content to its full height. Any changes to the code are highlighted in yellow. Set the window’s flag inside the Activity.java file. It must happen before super.onCreate() and can alternatively be set in the manifest.xml, if you prefer.

    @Override
     protected void onCreate(Bundle savedInstanceState) {

        requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);

	    // for v7support use: supportRequestWindowFeature
	    super.onCreate(savedInstanceState);
	    …
    }


The obvious drawback of the action bar overlay is that it now overlaps the window’s content.  While we want the drawer to extend the height of the window, we still want the other content to fit below the action bar. To make it look normal again, set a top padding on the layout. I prefer keeping a ‘host’ layout with this setting and then adding and removing child layouts with ‘match parent’ attributes. activity_main.xml will look something like this (modified from the Google sample project):

    <!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for 
    both width and height to consume the full space available. -->
    <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">

    <!-- As the main content view, the view below consumes the entire
         space available using match_parent in both dimensions. -->
    <FrameLayout
        android:id="@+id/content_frame"
        android:layout_width="match_parent"
        android:layout_height="match_parent" 
	    android:paddingTop=“?android:actionBarSize">

	<!-- (for v7 support use:  android:paddingTop=“@dimen/abc_action_bar_default_height_material”) -->

    <!-- android:layout_gravity="start" tells DrawerLayout to treat
         this as a sliding drawer on the left side for left-to-right
         languages and on the right side for right-to-left languages.
         The drawer is given a fixed width in dp and extends the full height of
         the container. A solid background is used for contrast
         with the content view. -->
    <ListView
        android:id="@+id/left_drawer"
        android:layout_width="240dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="#111"/>
        </android.support.v4.widget.DrawerLayout>


In order to be notified when the drawer is moving, and how far it has moved, add a callback method from the Android API. The sample app illustrates onDrawerOpen() and onDrawerClosed() but there are additional methods available. Add the onDrawerSlide() method in MainActivity.java within the anonymous class defined by ‘mDrawerToggle’. This portion of the file will then look like this, with the added changes in bold:

    @Override
    protected void onCreate(Bundle savedInstanceState) {

    …

        // ActionBarDrawerToggle ties together the proper interactions
        // between the sliding drawer and the action bar app icon
        mDrawerToggle = new ActionBarDrawerToggle(
                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.drawable.ic_drawer,  /* nav drawer image to replace 'Up' caret */
                R.string.drawer_open,  /* "open drawer" description for accessibility */
                R.string.drawer_close  /* "close drawer" description for accessibility */
                ) {
            public void onDrawerClosed(View view) {
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }

            public void onDrawerOpened(View drawerView) {
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
            }
     /**
     * {@link DrawerLayout.DrawerListener} callback method. If you do not use your
     * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call
     * through to this method from your own listener object.
     *
     * @param drawerView The child view that was moved
     * @param slideOffset The new offset of this drawer within its range, from 0-1
     */
            @Override
            public void onDrawerSlide(View drawerView, float slideOffset) {
                super.onDrawerSlide(drawerView, slideOffset);
            }
        };

      …
     }


To know where to put the window’s content, we can find the visible width of the drawer by treating ‘slideOffset’ as a percentage. The edge of the drawer is the x-location for the window’s content.
    
    @Override
    public void onDrawerSlide(View drawerView, float slideOffset) {
	    super.onDrawerSlide(drawerView, slideOffset);

        float xPositionOpenDrawer = mDrawerList.getWidth();
        float xPositionWindowContent = (slideOffset * xPositionOpenDrawer);
    }


There are two things to move with the drawer: the layout and the action bar. To move the layout, create a reference in MainActivity to the FrameLayout (i.e., ‘content_frame’) defined in activity_main.xml. For example:

mHostFragment = findViewById(R.id.content_frame);


Then we can change its position during drawer movement.
    
    @Override
    public void onDrawerSlide(View drawerView, float slideOffset) {
	   super.onDrawerSlide(drawerView, slideOffset);

   	   float xPositionOpenDrawer = mDrawerList.getWidth();
      float xPositionWindowContent = (slideOffset * xPositionOpenDrawer);
	   mHostFragment.setX(xPositionWindowContent);
    }


The final ingredient is to move the action bar. This requires accessing its associated view, which unfortunately is not straightforward, but is simple enough. This helper method within MainActivity does the trick on 4.0 and above:

    /** (For supporting ActionBarActivity, define packageName as:
    packageName = getActivity() instanceof ActionBarActivity ? 					
    getActivity().getPackageName() : “android"; 
    */

    public View getActionBarView() {

        Window window = this.getWindow();
        final View decorView = window.getDecorView();
        final String packageName =  "android";
        final int resId = this.getResources().getIdentifier("action_bar_container", "id", packageName);
        final View actionBarView = decorView.findViewById(resId);
        return actionBarView;
    }


Now the action bar can be moved with the content:
    
    @Override
    public void onDrawerSlide(View drawerView, float slideOffset) {
	   super.onDrawerSlide(drawerView, slideOffset);

	   float xPositionOpenDrawer = mDrawerList.getWidth();
       float xPositionWindowContent = (slideOffset * xPositionOpenDrawer);
	   mHostFragment.setX(xPositionWindowContent);
	   getActionBarView().setX(xPositionWindowContent);
    }


In Conclusion

The notable modifications are overriding onDrawerSlide() and making the action bar an overlay. Notice that providing v7 support requires changing the call name of some methods. In development, you can put all this logic into its own Fragment class outside the activity. In doing this, you would also move the ListView in activity_main (‘left_drawer’) into the new fragment, and replace it with a reference. activity_main.xml will then look something like this:

    <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" 
		  android:paddingTop="?android:actionBarSize">

      <fragment android:id="@+id/navigation_drawer"
        android:layout_width="@dimen/navigation_drawer_width"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:name="com.yourapp.NavigationDrawerFragment"
        tools:layout=“@layout/layout_with_listview_for_this_fragment” />
        </android.support.v4.widget.DrawerLayout>


If you have any questions or want to talk more about navigation drawer tips and tricks, give us a call. We’re always happy to chat!

778-372-2800


info@atimi.com

 


 

 
Twitter
 
LinkedIn
 
Facebook
 
Google