How to shift an ActionBar with a NavigationDrawer

What I want to do is to shift the ActionBar along with the NavigationDrawer when opening the box. Currently, I do not use third-party libraries and, if at all possible, I want to keep this. All I need is an implementation of a method like: getActionBarView.slide(dp);

This is the code I'm using to create a NavigationDrawer :

 mDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) { public void onDrawerClosed(View view) { invalidateOptionsMenu(); // calling onPrepareOptionsMenu() to hide action bar icons } @Override public void onDrawerSlide(View drawerView, float slideOffset) { if (getDeviceType(getApplicationContext()) == DEVICE_TYPE_PHONE) { drawerLayout.setScrimColor(Color.parseColor("#00FFFFFF")); float moveFactor = (listView.getWidth() * slideOffset); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { all_menu_container_parent.setTranslationX(moveFactor); } else { TranslateAnimation anim = new TranslateAnimation(lastTranslate, moveFactor, 0.0f, 0.0f); anim.setDuration(0); anim.setFillAfter(true); all_menu_container_parent.startAnimation(anim); lastTranslate = moveFactor; } } } public void onDrawerOpened(View drawerView) { // calling onPrepareOptionsMenu() to hide action bar icons } }; drawerLayout.setDrawerListener(mDrawerToggle); 

But he does not do what I want, he produces this:

I am currently stuck with this

I want to achieve this:

current screen shot from app

+15
java android android-actionbar navigation-drawer android-animation
May 21 '14 at 12:41
source share
1 answer

PLEASE NOTE: This answer was originally written when Android 4.4 (KitKat) was still fairly new. Starting with Android 5.0 and especially because of the introduction of ToolBar this answer cannot be considered relevant anymore! But from a technical point of view, for those of you who want to know about the internal workings of Android, this answer can still be of great value!

NavigationDrawer was specifically designed to be placed below the ActionBar , and there is no way to implement NavigationDrawer to make the ActionBar move with it - unless perhaps looking for the View that makes up the ActionBar and animating it along with the NavigationDrawer , but I would never recommend something like that because it would be difficult and error prone. In my opinion, you have only two options:

Since you said you did not want to use a library that implements a custom sliding menu, this is your only option, fortunately, it really is not as difficult as soon as you know how to do it.




1) The main explanation

You can move all the content of an Activity β€” I mean everything, including an ActionBar β€” by placing a marker or pad on the View that makes up the Activity . This View is the parent of the View with id android.R.id.content :

 View content = (View) activity.findViewById(android.R.id.content).getParent(); 

In Honeycomb (Android version 3.0 - API level 11) or higher - in other words, after the ActionBar been introduced - you need to use the fields to change the position of Activities , and in previous versions you need to use indents, To simplify this, I recommend creating helper methods that perform the correct actions for each level of the API. First, consider how to set the position of an 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); } } 

Please note that in both cases there is a negative margin or negative complement on opposite sides. This significantly increases the size of the Activity beyond its normal boundaries. This prevents the actual size of the Activity from changing when we move it somewhere.

We also need two methods to get the current position of the Activity . One for position x, one for position 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(); } } 

It is also very simple to add animation. The only important thing here is a little math to bring it back to life 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 the View in the location that opens by shifting the Activity by adding it to the parent View element:

 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’s almost all you need to create a basic sliding menu that works on most, if not all devices above Eclair (Android 2.1 - Level 7 API).




2) Animation Activity

The first part of creating a sliding menu is to move the Activity to the side. Therefore, we must first try to move the Activity as follows:
enter image description here

To create this, we just need to add the code above:

 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 as follows:

 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 a sliding menu

Now we want to open the menu when the Activity is deleted as follows: enter image description here
As you can see, this also pushes the ActionBar to the side.

The ActivitySlider class does not need to be modified in order to create a sliding menu, basically we just add two methods: showMenu() and hideMenu() . I will follow best practices and use Fragment as a sliding menu. The first thing we need is a View - for example, FrameLayout - as a container for our Fragment . We need to add this View to the parent element of the View 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 View container 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 Fragment menu, we add the setter method, which executes the FragmentTransaction and adds the Fragment menu 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 for convenience creates a Fragment from Class :

 public <T extends Fragment> void setMenuFragment(Class<T> cls) { Fragment fragment = Fragment.instantiate(this.activity, cls.getName()); setMenuFragment(fragment); } 

There is another important thing to keep in mind when it comes to the Fragment menu. We work much further in the View hierarchy than usual. Therefore, we must take into account such things as the height of the status bar. If we did not take this into account at the top of the Fragment menu, we would be hidden behind the status bar. You can get the height of the status bar as follows:

 Rect rectangle = new Rect(); Window window = this.activity.getWindow(); window.getDecorView().getWindowVisibleDisplayFrame(rectangle); final int statusBarHeight = rectangle.top; 

We must put the top margin in the View container of the Fragment menu as follows:

 // 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); 

Finally, we can put it all together:

 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 <T extends Fragment> void setMenuFragment(Class<T> 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 >= 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(); } } } 

I am using the static helper method in showMenu() to convert dip to pixels. Here is the code for this method:

 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; } 

You can use this new version of the ActivitySlider class as follows:

 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(); 



4) Conclusion and testing

Doing something like this is surprisingly easy when you know that you can just put a margin or addition on the View in the Activity . But the difficulty is getting it to work on many different devices. Implementations can change dramatically at several levels of the API and can have a significant impact on how this behaves. Having said that, any code I wrote here should work on most, if not all devices above Eclair (Android 2.1 - API level 7) without problems.
Of course, the solution I posted here is not complete, it can use a little extra polishing and cleaning, so feel free to refine the code to suit your needs.

I tested everything on the following devices:

HTC

  • One M8 (Android 4.4.2 - KitKat): Works
  • Sensation (Android 4.0.3 - Ice Cream Sandwich): Work
  • Desire (Android 2.3.3 - Gingerbread): Works
  • One (Android 4.4.2 - KitKat): Working

Samsung

  • Galaxy S3 Mini (Android 4.1.2 - Bean Jelly): Working
  • Galaxy S4 Mini (Android 4.2.2 - Jelly Bean): Working
  • Galaxy S4 (Android 4.4.2 - KitKat): Works
  • Galaxy S5 (Android 4.4.2 - KitKat): Working
  • Galaxy S Plus (Android 2.3.3 - Gingerbread): Work
  • Galaxy Ace (Android 2.3.6 - Gingerbread): Working
  • Galaxy S2 (Android 4.1.2 - Bean Jelly): Work
  • Galaxy S3 (Android 4.3 - Jelly Bean): Works
  • Galaxy Note 2 (Android 4.3 - Jelly Bean): Work
  • Galaxy Nexus (Android 4.2.1 - Jelly Bean): Work

Motorola

  • Moto G (Android 4.4.2 - KitKat): Work

LG

  • Nexus 5 (Android 4.4.2 - KitKat): Works

Zte

  • Blade (Android 2.1 - Eclair): Works

I hope I can help you, and if you have any further questions or anything else incomprehensible, do not hesitate to ask!

+59
May 30 '14 at 12:30
source share
β€” -



All Articles