× {{alert.msg}} Never ask again
Get notified about new tutorials RECEIVE NEW TUTORIALS

How to slide the ActionBar along with the NavigationDrawer

Xaver Kapeller
Mar 13, 2015
<p>You cannot use a <code>NavigationDrawer</code> for this purpose. The <code>NavigationDrawer</code> was specifically designed to be situated below the <code>ActionBar</code> and there is no way to implement the <code>NavigationDrawer</code> to make the <code>ActionBar</code> move with it - unless maybe looking for the <code>View</code> which makes up the <code>ActionBar</code> and animating it alongside the <code>NavigationDrawer</code>, but I would never recommend something like this as it would be difficult and error prone. In my opinion you only have two options:</p> <ol> <li>Using a <a href="https://github.com/jfeinstein10/SlidingMenu"><strong>library like the SlidingMenu</strong></a></li> <li>Implementing a custom sliding menu</li> </ol> <p>Since you said that you don't want to use a library implementing a custom sliding menu is your only option, fortunately this is really not that hard once you know how to do it.</p> <hr> <h2>1) Basic Explanation</h2> <p>You can move the whole content of the <code>Activity</code> - I mean everything including the <code>ActionBar</code> - by putting a margin or a padding on the <code>View</code> which makes up the <code>Activity</code>. This <code>View</code> is the parent of the <code>View</code> with the id <code>android.R.id.content</code>:</p> <pre><code>View content = (View) activity.findViewById(android.R.id.content).getParent(); </code></pre> <p>On Honeycomb (Android version 3.0 - API level 11) or above - in other words after the <code>ActionBar</code> was introduced - you need to use margins to change the <code>Activities</code> position and on previous versions you need to use a padding. To simplify this I recommend creating helper methods which perform the correct action for each API level. Let's first look at how to set the position of the <code>Activity</code>:</p> <pre><code>public void setActivityPosition(int x, int y) { // With this if statement we can check if the devices API level is above Honeycomb or below if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or abvoe we set a margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); contentParams.setMargins(x, y, -x, -y); this.content.setLayoutParams(contentParams); } else { // And on devices below Honeycomb we set a padding this.content.setPadding(x, y, -x, -y); } } </code></pre> <p>Notice that in both cases there is either a negative margin or a negative padding on the opposite sides. This is to essentially increase the size of the <code>Activity</code> beyond its normal bounds. This prevents the actual size of the <code>Activity</code> to change when we slide it somewhere.</p> <p>We additionally need two methods to get the current position of the <code>Activity</code>. One for the x position, one for the y position:</p> <pre><code>public int getActivityPositionX() { if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we return the left margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); return contentParams.leftMargin; } else { // On devices below Honeycomb we return the left padding return this.content.getPaddingLeft(); } } public int getActivityPositionY() { if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we return the top margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); return contentParams.topMargin; } else { // On devices below Honeycomb we return the top padding return this.content.getPaddingTop(); } } </code></pre> <p>It is also very simple to add animations. The only important thing here is a bit of math to animate it from its previous position to its new position</p> <pre><code>// We get the current position of the Activity final int currentX = getActivityPositionX(); final int currentY = getActivityPositionY(); // The new position is set setActivityPosition(x, y); // We animate the Activity to slide from its previous position to its new position TranslateAnimation animation = new TranslateAnimation(currentX - x, 0, currentY - y, 0); animation.setDuration(500); this.content.startAnimation(animation); </code></pre> <p>You can display a <code>View</code> at the location which is revealed by sliding away the <code>Activity</code> by adding it to the parent of the <code>View</code>:</p> <pre><code>final int currentX = getActivityPositionX(); FrameLayout menuContainer = new FrameLayout(context); // The width of the menu is equal to the x position of the `Activity` FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(currentX, ViewGroup.LayoutParams.MATCH_PARENT); menuContainer.setLayoutParams(params); ViewGroup parent = (ViewGroup) content.getParent(); parent.addView(menuContainer); </code></pre> <p>And that is pretty much all you need to create a basic sliding menu that works on most if not all devices above Eclair (Android 2.1 - API level 7).</p> <hr> <h2>2) Animating the <code>Activity</code></h2> <p>The first part of creating a sliding menu is making the <code>Activity</code> move out of the way. As such we should first try to move the <code>Activity</code> around like this:<br> <img src="http://i.stack.imgur.com/993vv.gif" alt="enter image description here"></p> <p>To create this we just have to put the code above together:</p> <pre><code>import android.os.Build; import android.support.v4.app.FragmentActivity; import android.view.View; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; public class ActivitySlider { private final FragmentActivity activity; private final View content; public ActivitySlider(FragmentActivity activity) { this.activity = activity; // Here we get the content View from the Activity. this.content = (View) activity.findViewById(android.R.id.content).getParent(); } public void slideTo(int x, int y) { // We get the current position of the Activity final int currentX = getActivityPositionX(); final int currentY = getActivityPositionY(); // The new position is set setActivityPosition(x, y); // We animate the Activity to slide from its previous position to its new position TranslateAnimation animation = new TranslateAnimation(currentX - x, 0, currentY - y, 0); animation.setDuration(500); this.content.startAnimation(animation); } public void setActivityPosition(int x, int y) { // With this if statement we can check if the devices API level is above Honeycomb or below if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we set a margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); contentParams.setMargins(x, y, -x, -y); this.content.setLayoutParams(contentParams); } else { // And on devices below Honeycomb we set a padding this.content.setPadding(x, y, -x, -y); } } public int getActivityPositionX() { if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we return the left margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); return contentParams.leftMargin; } else { // On devices below Honeycomb we return the left padding return this.content.getPaddingLeft(); } } public int getActivityPositionY() { if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we return the top margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); return contentParams.topMargin; } else { // On devices below Honeycomb we return the top padding return this.content.getPaddingTop(); } } } </code></pre> <p>You can use the <code>ActivitySlider</code> class like this:</p> <pre><code>ActivitySlider slider = new ActivitySlider(activity); // This would move the Activity 400 pixel to the right and 100 pixel down slider.slideTo(400, 100); </code></pre> <hr> <h2>3) Adding the sliding menu</h2> <p>Now we want to reveal a menu when the <code>Activity</code> moves out of the way like this: <img src="http://i.stack.imgur.com/wYSpD.gif" alt="enter image description here"><br> As you can see it also pushes the <code>ActionBar</code> to the side.</p> <p>The <code>ActivitySlider</code> class does not need to be modified that much to create a sliding menu, basically we just add two methods, <code>showMenu()</code> and <code>hideMenu()</code>. I will stick to best practices and use a <code>Fragment</code> as the sliding menu. The first thing we need need is a <code>View</code> - for example a <code>FrameLayout</code> - as a container for our <code>Fragment</code>. We need to add this <code>View</code> to the parent of the <code>View</code> of the <code>Activity</code>:</p> <pre><code>// We get the View of the Activity View content = (View) activity.findViewById(android.R.id.content).getParent(); // And its parent ViewGroup parent = (ViewGroup) content.getParent(); // The container for the menu Fragment is a FrameLayout // We set an id so we can perform FragmentTransactions later on FrameLayout menuContainer = new FrameLayout(this.activity); menuContainer.setId(R.id.flMenuContainer); // The visibility is set to GONE because the menu is initially hidden menuContainer.setVisibility(View.GONE); // The container for the menu Fragment is added to the parent parent.addView(menuContainer); </code></pre> <p>Since we set the visibility of the container <code>View</code> to VISIBLE only when the sliding menu is actually open we can use the following method to check if the menu is open or closed:</p> <pre><code>public boolean isMenuVisible() { return this.menuContainer.getVisibility() == View.VISIBLE; } </code></pre> <p>To set the menu <code>Fragment</code> we add a setter method that performs a <code>FragmentTransaction</code> and adds the menu <code>Fragment</code> to the <code>FrameLayout</code>:</p> <pre><code>public void setMenuFragment(Fragment fragment) { FragmentManager manager = this.activity.getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); transaction.replace(R.id.flMenuContainer, fragment); transaction.commit(); } </code></pre> <p>I also tend to add a second setter which instantiates the <code>Fragment</code> from a <code>Class</code> for convenience:</p> <pre><code>public &lt;T extends Fragment&gt; void setMenuFragment(Class&lt;T&gt; cls) { Fragment fragment = Fragment.instantiate(this.activity, cls.getName()); setMenuFragment(fragment); } </code></pre> <p>There is one additional important thing to consider when it comes to the menu <code>Fragment</code>. We are operating much further up in the <code>View</code> hierarchy than normally. As such we have to take things like the height of the status bar into account. If we didn't account for this the top of the menu <code>Fragment</code> would we be hidden behind the status bar. You can get the height of the status bar like this:</p> <pre><code>Rect rectangle = new Rect(); Window window = this.activity.getWindow(); window.getDecorView().getWindowVisibleDisplayFrame(rectangle); final int statusBarHeight = rectangle.top; </code></pre> <p>We have to put a top margin on the container <code>View</code> of the menu <code>Fragment</code> like this:</p> <pre><code>// These are the LayoutParams for the menu Fragment FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT); // We put a top margin on the menu Fragment container which is equal to the status bar height params.setMargins(0, statusBarHeight, 0, 0); menuContainer.setLayoutParams(fragmentParams); </code></pre> <p>Finally we can put all this together:</p> <pre><code>import android.graphics.Rect; import android.os.Build; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import at.test.app.R; import at.test.app.helper.LayoutHelper; public class ActivitySlider { private final FragmentActivity activity; private final View content; private final FrameLayout menuContainer; public ActivitySlider(FragmentActivity activity) { this.activity = activity; // We get the View of the Activity this.content = (View) activity.findViewById(android.R.id.content).getParent(); // And its parent ViewGroup parent = (ViewGroup) this.content.getParent(); // The container for the menu Fragment is added to the parent. We set an id so we can perform FragmentTransactions later on this.menuContainer = new FrameLayout(this.activity); this.menuContainer.setId(R.id.flMenuContainer); // We set visibility to GONE because the menu is initially hidden this.menuContainer.setVisibility(View.GONE); parent.addView(this.menuContainer); } public &lt;T extends Fragment&gt; void setMenuFragment(Class&lt;T&gt; cls) { Fragment fragment = Fragment.instantiate(this.activity, cls.getName()); setMenuFragment(fragment); } public void setMenuFragment(Fragment fragment) { FragmentManager manager = this.activity.getSupportFragmentManager(); FragmentTransaction transaction = manager.beginTransaction(); transaction.replace(R.id.flMenuContainer, fragment); transaction.commit(); } public boolean isMenuVisible() { return this.menuContainer.getVisibility() == View.VISIBLE; } // We pass the width of the menu in dip to showMenu() public void showMenu(int dpWidth) { // We convert the width from dip into pixels final int menuWidth = LayoutHelper.dpToPixel(this.activity, dpWidth); // We move the Activity out of the way slideTo(menuWidth, 0); // We have to take the height of the status bar at the top into account! Rect rectangle = new Rect(); Window window = this.activity.getWindow(); window.getDecorView().getWindowVisibleDisplayFrame(rectangle); final int statusBarHeight = rectangle.top; // These are the LayoutParams for the menu Fragment FrameLayout.LayoutParams fragmentParams = new FrameLayout.LayoutParams(menuWidth, ViewGroup.LayoutParams.MATCH_PARENT); // We put a top margin on the menu Fragment container which is equal to the status bar height fragmentParams.setMargins(0, statusBarHeight, 0, 0); this.menuContainer.setLayoutParams(fragmentParams); // Perform the animation only if the menu is not visible if(!isMenuVisible()) { // Visibility of the menu container View is set to VISIBLE this.menuContainer.setVisibility(View.VISIBLE); // The menu slides in from the right TranslateAnimation animation = new TranslateAnimation(-menuWidth, 0, 0, 0); animation.setDuration(500); this.menuContainer.startAnimation(animation); } } public void hideMenu() { // We can only hide the menu if it is visible if(isMenuVisible()) { // We slide the Activity back to its original position slideTo(0, 0); // We need the width of the menu to properly animate it final int menuWidth = this.menuContainer.getWidth(); // Now we need an extra animation for the menu fragment container TranslateAnimation menuAnimation = new TranslateAnimation(0, -menuWidth, 0, 0); menuAnimation.setDuration(500); menuAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { // As soon as the hide animation is finished we set the visibility of the fragment container back to GONE menuContainer.setVisibility(View.GONE); } @Override public void onAnimationRepeat(Animation animation) { } }); this.menuContainer.startAnimation(menuAnimation); } } public void slideTo(int x, int y) { // We get the current position of the Activity final int currentX = getActivityPositionX(); final int currentY = getActivityPositionY(); // The new position is set setActivityPosition(x, y); // We animate the Activity to slide from its previous position to its new position TranslateAnimation animation = new TranslateAnimation(currentX - x, 0, currentY - y, 0); animation.setDuration(500); this.content.startAnimation(animation); } public void setActivityPosition(int x, int y) { // With this if statement we can check if the devices API level is above Honeycomb or below if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we set a margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); contentParams.setMargins(x, y, -x, -y); this.content.setLayoutParams(contentParams); } else { // And on devices below Honeycomb we set a padding this.content.setPadding(x, y, -x, -y); } } public int getActivityPositionX() { if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we return the left margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); return contentParams.leftMargin; } else { // On devices below Honeycomb we return the left padding return this.content.getPaddingLeft(); } } public int getActivityPositionY() { if(Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.HONEYCOMB) { // On Honeycomb or above we return the top margin FrameLayout.LayoutParams contentParams = (FrameLayout.LayoutParams) this.content.getLayoutParams(); return contentParams.topMargin; } else { // On devices below Honeycomb we return the top padding return this.content.getPaddingTop(); } } } </code></pre> <p>I use a static helper method in <code>showMenu()</code> to convert dip to pixels. Here is the code of this method:</p> <pre><code>public static int dpToPixel(Context context, int dp) { float scale = getDisplayDensityFactor(context); return (int) (dp * scale + 0.5f); } private static float getDisplayDensityFactor(Context context) { if (context != null) { Resources res = context.getResources(); if (res != null) { DisplayMetrics metrics = res.getDisplayMetrics(); if(metrics != null) { return metrics.density; } } } return 1.0f; } </code></pre> <p>You can use this new version of the <code>ActivitySlider</code> class like this:</p> <pre><code>ActivitySlider slider = new ActivitySlider(activity); slider.setMenuFragment(MenuFragment.class); // The menu is shown with a width of 200 dip slider.showMenu(200); ... // Hide the menu again slider.hideMenu(); </code></pre> <hr> <h2>4) Conclusion &amp; Testing</h2> <p>Doing something like this is surprisingly easy when you know that you can simply put a margin or a padding on the <code>View</code> of the <code>Activity</code>. But the difficulty is in making it work on a lot of different devices. Implementations can change a lot across multiple API Levels and that can have considerable influence on how this behaves. Having said that any code I posted here should work on most if not all devices above Eclair (Android 2.1 - API level 7) without any problems.<br> Of course the solution I posted here is not complete, it could use a little extra polishing and cleaning up, so feel free to improve the code to suit your needs!</p> <p>I have tested everything on the following devices:</p> <blockquote> <p>HTC</p> <ul> <li><strong>One M8 (Android 4.4.2 - KitKat):</strong> Working</li> <li><strong>Sensation (Android 4.0.3 - Ice Cream Sandwich):</strong> Working</li> <li><strong>Desire (Android 2.3.3 - Gingerbread):</strong> Working</li> <li><strong>One (Android 4.4.2 - KitKat):</strong> Working</li> </ul> <p>Samsung</p> <ul> <li><strong>Galaxy S3 Mini (Android 4.1.2 - Jelly Bean):</strong> Working</li> <li><strong>Galaxy S4 Mini (Android 4.2.2 - Jelly Bean):</strong> Working</li> <li><strong>Galaxy S4 (Android 4.4.2 - KitKat):</strong> Working</li> <li><strong>Galaxy S5 (Android 4.4.2 - KitKat):</strong> Working</li> <li><strong>Galaxy S Plus (Android 2.3.3 - Gingerbread):</strong> Working</li> <li><strong>Galaxy Ace (Android 2.3.6 - Gingerbread):</strong> Working</li> <li><strong>Galaxy S2 (Android 4.1.2 - Jelly Bean):</strong> Working</li> <li><strong>Galaxy S3 (Android 4.3 - Jelly Bean):</strong> Working</li> <li><strong>Galaxy Note 2 (Android 4.3 - Jelly Bean):</strong> Working</li> <li><strong>Galaxy Nexus (Android 4.2.1 - Jelly Bean):</strong> Working</li> </ul> <p>Motorola</p> <ul> <li><strong>Moto G (Android 4.4.2 - KitKat):</strong> Working</li> </ul> <p>LG</p> <ul> <li><strong>Nexus 5 (Android 4.4.2 - KitKat):</strong> Working</li> </ul> <p>ZTE</p> <ul> <li><strong>Blade (Android 2.1 - Eclair):</strong> Working</li> </ul> </blockquote> <p>I hope I could help you and if you have any further questions or anything else is unclear please feel free to ask!</p> <p>This tip was originally posted on <a href="http://stackoverflow.com/questions/23783496/How%20to%20slide%20the%20ActionBar%20along%20with%20the%20NavigationDrawer/23954699">Stack Overflow</a>.</p>

Get New Tutorials Delivered to Your Inbox

New tutorials will be sent to your Inbox once a week.

comments powered by Disqus