PLEASE NOTE: This answer was originally written when Android 4.4 (KitKat) was still pretty new. Since Android 5.0 and especially
because of the introduction of the ToolBar
this answer cannot be
considered up-to-date anymore! But from a technical perspective and for
those of you who want to learn about the inner workings of Android
this answer might still hold a lot of value!
The NavigationDrawer
was specifically designed to be situated below the ActionBar
and there is no way to implement the NavigationDrawer
to make the ActionBar
move with it - unless maybe looking for the View
which makes up the ActionBar
and animating it alongside the NavigationDrawer
, but I would never recommend something like this as it would be difficult and error prone. In my opinion you only have two options:
- Using a library like the SlidingMenu
- Implementing a custom sliding menu
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.
1) Basic Explanation
You can move the whole content of the Activity
- I mean everything including the ActionBar
- by putting a margin or a padding on the View
which makes up the Activity
. This View
is the parent of the View
with the id android.R.id.content
:
View content = (View) activity.findViewById(android.R.id.content).getParent();
On Honeycomb (Android version 3.0 - API level 11) or above - in other words after the ActionBar
was introduced - you need to use margins to change the Activities
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 Activity
:
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 >= 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);
}
}
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 Activity
beyond its normal bounds. This prevents the actual size of the Activity
to change when we slide it somewhere.
We additionally need two methods to get the current position of the Activity
. One for the x position, one for the y position:
public int getActivityPositionX() {
if(Build.VERSION.SDK_INT >= 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 >= 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();
}
}
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
// 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);
You can display a View
at the location which is revealed by sliding away the Activity
by adding it to the parent of the View
:
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);
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).
2) Animating the Activity
The first part of creating a sliding menu is making the Activity
move out of the way. As such we should first try to move the Activity
around like this:
To create this we just have to put the code above together:
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 >= 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 >= 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 >= 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();
}
}
}
You can use the ActivitySlider
class like this:
ActivitySlider slider = new ActivitySlider(activity);
// This would move the Activity 400 pixel to the right and 100 pixel down
slider.slideTo(400, 100);
3) Adding the sliding menu
Now we want to reveal a menu when the Activity
moves out of the way like this:
As you can see it also pushes the ActionBar
to the side.
The ActivitySlider
class does not need to be modified that much to create a sliding menu, basically we just add two methods, showMenu()
and hideMenu()
. I will stick to best practices and use a Fragment
as the sliding menu. The first thing we need need is a View
- for example a FrameLayout
- as a container for our Fragment
. We need to add this View
to the parent of the View
of the Activity
:
// 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);
Since we set the visibility of the container View
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:
public boolean isMenuVisible() {
return this.menuContainer.getVisibility() == View.VISIBLE;
}
To set the menu Fragment
we add a setter method that performs a FragmentTransaction
and adds the menu Fragment
to the FrameLayout
:
public void setMenuFragment(Fragment fragment) {
FragmentManager manager = this.activity.getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.flMenuContainer, fragment);
transaction.commit();
}
I also tend to add a second setter which instantiates the Fragment
from a Class
for convenience:
public <T extends Fragment> void setMenuFragment(Class<T> cls) {
Fragment fragment = Fragment.instantiate(this.activity, cls.getName());
setMenuFragment(fragment);
}
There is one additional important thing to consider when it comes to the menu Fragment
. We are operating muc