As per the Android guidelines, I implemented an ActionBarDrawerToggle that switches to the global context onDrawerOpened and a local context onDrawerClosed. This context switch involves changing the action bar items as well as the action bar title, and is really straightforward. The problem is, if I am navigating to a new screen then the action bar's title will change twice, once for the switch back to local context and again for the new screen that the user is navigating too. This seems clunky, and I can't seem to figure out a way to implement the title change such that the user doesn't see it twice.
It seems like the best way to handle this is to have a condition you check when the drawer closes.
Here's a short example:
private boolean mNoTitleChange;
private int mPosition = -1;
#Override
public void onDrawerClosed(View view) {
if (mNoTitleChange) {
startActivity(new Intent(CurrentActivity.this, NewActivity.class));
mNoTitleChange = false;
return;
}
getActionBar().setTitle(mTitle);
invalidateOptionsMenu();
}
#Override
protected void onResume() {
super.onResume();
if (mPosition != -1) {
setTitle(mYourTitles[mPosition]);
mPosition = -1;
}
}
Whenever you select an item in your DrawerLayout, adjust the boolean as needed.
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mNoTitleChange = true;
mPosition = position;
mDrawerLayout.closeDrawer(mDrawerList);
}
Related
There is a left drawer and a fragment that changes when an item in the navigation drawer is selected. The problem is that, if the "Item A" is already selected, and the user select it again, the fragment is changed for the same fragment.
To prevent this behavior, I need to know which item is already selected. I was using a attribute mLastSelectedItemPosition, but i don't think this is a good solution. Is there any way to get the current checked item from the navigation drawer?
class OnNavigationItemSelectedListener implements NavigationView.OnNavigationItemSelectedListener {
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
if (mLastSelectedItemPosition != menuItem.getOrder()) {
// Is not the same item, so I can change the fragment.
mLastSelectedItemPosition = menuItem.getOrder();
}
mDrawerLayout.closeDrawers();
return true;
}
}
}
I do the exact same thing, have a int variable holding the selected position.
One thing to keep in mind is to save this variable onInstaceChange, because if you don`t you might be out-of-date with your drawer.
So, with that said, some code:
private static final String MENU_ITEM = "menu_item";
private int menuItemId;
...
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(MenuItem item) {
if (menuItemId == item.getItemId()) {
drawerLayout.closeDrawer(navigationView);
return false;
switch (item.getItemId()) {
....
}
...
drawerLayout.closeDrawer(navigationView);
item.setChecked(true);
menuItemId = item.getItemId();
return true;
}
});
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(MENU_ITEM, menuItemId);
}
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
this.menuItemId = savedInstanceState.getInt(MENU_ITEM);
}
Something like that !
I think this will help you.
private class SlideMenuClickListener implements
ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// display view for selected nav drawer item
displayView(position);
Toast.makeText(getApplicationContext(),"You have selected " +position,Toast.LENGTH_LONG).show();
}
}
I am creating an Android app using one main activity and multiple fragments. I am using the navigation drawer which has two items in it - Fragment A and Fragment B. From Fragment A you can go to 2 lower level fragments and the same for Fragment B. In my case there is a button in Fragment B.1 and Fragment B.2 which allows you to go directly to Fragment A.3. (see image below)
This all works well with the back stack however I want to also implement the home Up button. Usually if you are using multiple activities you would use the NavUtils class but I can't use this for fragments. Instead I have implemented the solution found here. Unfortunately this does not follow the navigation pattern correctly in my case as when I click on the button in Fragment B.2 to go to Fragment A.3 and then click the the up button it takes me back to Fragment B.2 instead of up to Fragment A.2.
The navigation pattern I am talking about is shown here:
i.stack.imgur.com/EsoTl.png
My app navigation looks like this:
i.stack.imgur.com/8WjBr.png
The code:
private FragmentManager.OnBackStackChangedListener
mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
setActionBarArrowDependingOnFragmentsBackStack();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// get the title of the current page
title = drawerTitle = getTitle();
// load slide menu item titles
navMenuTitles = getResources().getStringArray(R.array.nav_menu_items);
navMenuLayout = (DrawerLayout) findViewById(R.id.nav_menu_layout);
navMenuList = (ListView) findViewById(R.id.nav_menu_list);
navMenuItems = new ArrayList<NavMenuItem>();
// Instantiate a NavMenuItem object and add it to an ArrayList.
for (int i = 0; i < navMenuTitles.length; i++){
navMenuItems.add(new NavMenuItem(navMenuTitles[i]));
}
// setting the nav menu list adapter
adapter = new NavMenuListAdapter(getApplicationContext(),navMenuItems);
navMenuList.setAdapter(adapter);
// enabling action bar app icon and behaving it as toggle button
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);
// setup the onItemClickListener to load correct page when user clicks on navigation item
navMenuList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
displayFragment(position);
}
});
mDrawerToggle = new ActionBarDrawerToggle(this, navMenuLayout,
R.drawable.ic_drawer, //nav menu toggle icon
R.string.app_name, // nav drawer open - description for accessibility
R.string.app_name // nav drawer close - description for accessibility
){
public void onDrawerClosed(View view) {
getActionBar().setTitle(title);
}
public void onDrawerOpened(View drawerView) {
getActionBar().setTitle(drawerTitle);
}
};
navMenuLayout.setDrawerListener(mDrawerToggle);
getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
// if this is the first time the app is being opened then open the first page from the list.
if (savedInstanceState == null) {
// on first time display view for first nav item
displayFragment(0);
}
}
#Override
protected void onDestroy() {
getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
super.onDestroy();
}
private void setActionBarArrowDependingOnFragmentsBackStack() {
int backStackEntryCount =
getSupportFragmentManager().getBackStackEntryCount();
mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.isDrawerIndicatorEnabled() &&
mDrawerToggle.onOptionsItemSelected(item)) {
return true;
} else if (item.getItemId() == android.R.id.home &&
getSupportFragmentManager().popBackStackImmediate()) {
return true;
} else {
switch (item.getItemId()) {
// handle other items in actionbar
default:
return super.onOptionsItemSelected(item);
}
}
}
I'm using Navigation Drawer in my application.
When the user clicks on any of the menu item in drawer, it opens a new Activity (not fragment).
Now, I'm using slide_right_in/slide_left_out animation as transition between activities.
The code works, but these animations conflicts with the closing animation of Navigation Drawer, as even before the drawer gets completely closed, the current activity starts sliding out to left & next activity starts sliding in from right.
So, is there any way to start the animation only after drawer is completely closed?
Thank You
You can open Activity with a delay. For example, in such a way Activity will be started after 250ms:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Intent intent = new Intent(<filter>);
startActivity(intent);
finish();
}
}, 250);
mDrawerLayout.closeDrawer(mDrawerList);
I did this somehow similiar to Jan.
Select an item
If an item gets clicked, i save its id and close the Drawer:
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener()
{
#Override
public boolean onNavigationItemSelected(MenuItem menuItem)
{
clickedItem = menuItem.getItemId();
drawerLayout.closeDrawers();
return true;
}
});
Listen for drawer close
If the Drawer gets closed i listen for it and check if an item has been clicked. If it has i call my method to handle the navigation click.
drawerToggle = new ActionBarDrawerToggle(activity, drawerLayout, R.string.accessibility_open_nav, R.string.accessibility_open_nav)
{
#Override
public void onDrawerClosed(View drawerView)
{
super.onDrawerClosed(drawerView);
if(clickedItem != 0)
{
handleNavigationClick();
}
}
};
drawerLayout.setDrawerListener(drawerToggle);
Handle the click
Here i react to the item click on open intents in this case (simplified). You need to reset the clickedItem here to 0. That is, because if you go back to an activity, open and close the drawer, it would still have the clickedItem number and would handle the click again.
private void handleItemClick()
{
switch (clickedItem)
{
case R.id.item_1:
do_something_1;
break;
case R.id.item_2:
do_something_2;
break;
}
clickedItem = 0;
}
Every answer is so complicated..... It's so easy.
Just add a drawerlistener and do something in onClosed() method:
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
when you select a item from navigation drawer you will call this method to close the drawer:
drawer.closeDrawer(GravityCompat.START);
after the above method call just add below lines and do whatever you want:
drawer.addDrawerListener(new DrawerLayout.DrawerListener() {
#Override
public void onDrawerSlide(#NonNull View drawerView, float slideOffset) {
}
#Override
public void onDrawerOpened(#NonNull View drawerView) {
}
#Override
public void onDrawerClosed(#NonNull View drawerView) {
startActivity(finalIntent);
// Or else do something here....
}
#Override
public void onDrawerStateChanged(int newState) {
}
});
Don't close navigation drawer. It will slide with the old activity. Or call startActivity after drawerLayout.closeDrawer(drawerList);
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
drawerLayout.closeDrawer(drawerList);
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
//set animation here
startActivity(intent);
finish();
}
The nikis's answer does not cover all cases. If your Fragment or Activity contains map or CameraView it may costs more time. And differences between simple screen and screen with map too big so it's hard to select a good delay.
The best approach is to send callback to NavigationDrawer from just opened Activity/Fragment in onResume(). Then close drawer in this callback.
I extend all my activities where I want to have a Navigation drawer by DrawerActivity class. Because I had a little animation conflict as well so I start my Activities after Navigation drawer is completely closed. This is implementation which covers all cases:
public class DrawerActivity extends AppCompatActivity {
private String[] mMenuTitles;
private DrawerLayout mDrawerLayout;
private ListView mDrawerList;
private CharSequence mTitle;
private ActionBarDrawerToggle mDrawerToggle;
private Boolean isItemClicked = false;
protected void onCreateDrawer() {
mTitle = getResources().getString(R.string.app_name);
mMenuTitles = new String[]{getString(R.string.drawer_item1), getString(R.string.drawer_item2), getString(R.string.drawer_item3)};
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerLayout.setScrimColor(getResources().getColor(R.color.black_transparent_30));
// mDrawerLayout.setScrimColor(Color.TRANSPARENT);
mDrawerList = (ListView) findViewById(R.id.left_drawer);
// Set the adapter for the list view
mDrawerList.setAdapter(new ArrayAdapter<String>(this,
R.layout.drawer_list_item, mMenuTitles));
// Set the list's click listener
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
mDrawerToggle = new ActionBarDrawerToggle(
this, /* host Activity */
mDrawerLayout, /* DrawerLayout object */
// R.mipmap.ic_drawer, /* nav drawer icon to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description */
R.string.drawer_close /* "close drawer" description */
) {
If item is clicked, I start activity on checked position.
/** Called when a drawer has settled in a completely closed state. */
public void onDrawerClosed(View view) {
if (isItemClicked) {
int position = mDrawerList.getCheckedItemPosition();
startMyActivity(position);
isItemClicked = false;
}
Method setCheckedItem is overriden in extended activities for showing checked item consistently.
setCheckedItem(mDrawerList);
// getSupportActionBar().setTitle(mTitle);
}
/** Called when a drawer has settled in a completely open state. */
public void onDrawerOpened(View drawerView) {
// getSupportActionBar().setTitle(mTitle);
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
public void setCheckedItem(ListView mDrawerList) {
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
setCheckedItem(mDrawerList);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDrawerToggle.onConfigurationChanged(newConfig);
}
private void selectItem(int position) {
mDrawerList.setItemChecked(position, true);
// setTitle(mMenuTitles[position]);
// Toast.makeText(this, mTitle, Toast.LENGTH_SHORT).show();
// startMyActivity(position);
mDrawerLayout.closeDrawer(mDrawerList);
}
public void startMyActivity(int position) {
//is overriden in extended activities for being able to send data to another activity
}
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
Item is clicked so I set my helper atribute to true.
public void onItemClick(AdapterView parent, View view, int position, long id) {
isItemClicked = true;
selectItem(position);
}
}
public void setTitle(CharSequence title) {
mTitle = title;
getSupportActionBar().setTitle(mTitle);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
// Pass the event to ActionBarDrawerToggle, if it returns
// true, then it has handled the app icon touch event
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}}
I have found a simple way to get rid of id you may try this and its working and tested.
OLD ANSWER:
int id = -1;
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
id = item.getItemId();
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
drawer.setDrawerListener(new DrawerLayout.DrawerListener() {
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
}
#Override
public void onDrawerOpened(View drawerView) {
}
#Override
public void onDrawerClosed(View drawerView) {
if (id == R.id.NAV_YOUR_ACTIVITY_ONE) {
Intent activityIntent = new Intent(getApplicationContext(), YOUR_ACTIVITY_ONE.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(activityIntent);
}
else if (id == R.id.NAV_YOUR_ACTIVITY_TWO) {
Intent activityIntent = new Intent(getApplicationContext(), YOUR_ACTIVITY_TWO.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(activityIntent);
}
//do not forget to set id again to -1 or else it will cause problem
id=-1;
}
#Override
public void onDrawerStateChanged(int newState) {
}
}
}
UPDATE ANSWER:
You can your ActionBarDrawerToggle too for example observe below code.
int id = -1;
//Use this function for click effect be performed on drawer item clicked
public void perfromDrawerNavigation(){
if (id == R.id.NAV_YOUR_ACTIVITY_ONE) {
Intent activityIntent = new Intent(getApplicationContext(), YOUR_ACTIVITY_ONE.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(activityIntent);
}
else if (id == R.id.NAV_YOUR_ACTIVITY_TWO) {
Intent activityIntent = new Intent(getApplicationContext(), YOUR_ACTIVITY_TWO.class);
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(activityIntent);
}
//do not forget to set id again to -1 or else it will cause problem
id=-1;
}
//you can write this code in onCreate() also
public void initControl(){
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(
getResources().getString(R.string.activity));
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close){
#Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if(id != -1){
perfromDrawerNavigation();
}
}
};
drawer.addDrawerListener(toggle);
toggle.setDrawerIndicatorEnabled(true);
toggle.syncState();
}
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
id = item.getItemId();
drawer.closeDrawer(GravityCompat.START);
}
I'm using the Google DrawerLayout.
When an item gets clicked, the drawer is smoothly closed and an Activity will be launched. Turning these activities into Fragments is not an option. Because of this, launching an activity and then closing the drawer is also not an option. Closing the drawer and launching the activity at the same time will make the closing animation stutter.
Given that I want to smoothly close it first, and then launch the activity, I have a problem with the latency between when a user clicks on the drawer item, and when they see the activity they wanted to go to.
This is what the click listener for each item looks like.
final View.OnClickListener mainItemClickListener = new View.OnClickListener() {
#Override
public void onClick(final View v) {
mViewToLaunch = v;
mDrawerLayout.closeDrawers();
}
};
My activity is also the DrawerListener, its onDrawerClosed method looks like:
#Override
public synchronized void onDrawerClosed(final View view) {
if (mViewToLaunch != null) {
onDrawerItemSelection(mViewToLaunch);
mViewToLaunch = null;
}
}
onDrawerItemSelection just launches one of the five activities.
I do nothing on onPause of the DrawerActivity.
I am instrumenting this and it takes on average from 500-650ms from the moment onClick is called, to the moment onDrawerClosed ends.
There is a noticeable lag, once the drawer closes, before the corresponding activity is launched.
I realize a couple of things are happening:
The closing animation takes place, which is a couple of milliseconds right there (let's say 300).
Then there's probably some latency between the drawer visually closing and its listener getting fired. I'm trying to figure out exactly how much of this is happening by looking at DrawerLayout source but haven't figured it out yet.
Then there's the amount of time it takes for the launched activity to perform its startup lifecycle methods up to, and including, onResume. I have not instrumented this yet but I estimate about 200-300ms.
This seems like a problem where going down the wrong path would be quite costly so I want to make sure I fully understand it.
One solution is just to skip the closing animation but I was hoping to keep it around.
How can I decrease my transition time as much as possible?
According the docs,
Avoid performing expensive operations such as layout during animation as it can cause stuttering; try to perform expensive operations during the STATE_IDLE state.
Instead of using a Handler and hard-coding the time delay, you can override the onDrawerStateChanged method of ActionBarDrawerToggle (which implements DrawerLayout.DrawerListener), so that you can perform the expensive operations when the drawer is fully closed.
Inside MainActivity,
private class SmoothActionBarDrawerToggle extends ActionBarDrawerToggle {
private Runnable runnable;
public SmoothActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar, int openDrawerContentDescRes, int closeDrawerContentDescRes) {
super(activity, drawerLayout, toolbar, openDrawerContentDescRes, closeDrawerContentDescRes);
}
#Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
invalidateOptionsMenu();
}
#Override
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
invalidateOptionsMenu();
}
#Override
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
if (runnable != null && newState == DrawerLayout.STATE_IDLE) {
runnable.run();
runnable = null;
}
}
public void runWhenIdle(Runnable runnable) {
this.runnable = runnable;
}
}
Set the DrawerListener in onCreate:
mDrawerToggle = new SmoothActionBarDrawerToggle(this, mDrawerLayout, mToolbar, R.string.open, R.string.close);
mDrawerLayout.setDrawerListener(mDrawerToggle);
Finally,
private void selectItem(int position) {
switch (position) {
case DRAWER_ITEM_SETTINGS: {
mDrawerToggle.runWhenIdle(new Runnable() {
#Override
public void run() {
Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
startActivity(intent);
}
});
mDrawerLayout.closeDrawers();
break;
}
case DRAWER_ITEM_HELP: {
mDrawerToggle.runWhenIdle(new Runnable() {
#Override
public void run() {
Intent intent = new Intent(MainActivity.this, HelpActivity.class);
startActivity(intent);
}
});
mDrawerLayout.closeDrawers();
break;
}
}
}
I was facing same issue with DrawerLayout.
I have research for that and then find one nice solution for it.
What i am doing is.....
If you refer Android Sample app for the DrawerLayout then check the code for selectItem(position);
In this function based on the position selection fragment is called. I have modify it with below code as per my need and works fine with no animation close stutter.
private void selectItem(final int position) {
//Toast.makeText(getApplicationContext(), "Clicked", Toast.LENGTH_SHORT).show();
mDrawerLayout.closeDrawer(drawerMain);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
Fragment fragment = new TimelineFragment(UserTimeLineActivity.this);
Bundle args = new Bundle();
args.putInt(TimelineFragment.ARG_PLANET_NUMBER, position);
fragment.setArguments(args);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
// update selected item and title, then close the drawer
mCategoryDrawerList.setItemChecked(position, true);
setTitle("TimeLine: " + mCategolyTitles[position]);
}
}, 200);
// update the main content by replacing fragments
}
Here i am first closing the DrawerLayout. which takes approx 250 miliseconds. and then my handler will call the fragment. Which works smooth and as per the requirement.
Hope it will also helpful to you.
Enjoy Coding... :)
So I seem to have solved the problem with a reasonable solution.
The largest source of perceivable latency was the delay between when the drawer was visually closed, and when onDrawerClosed was called. I solved this by posting a Runnable to a private Handler that launches the intended activity at some specified delay. This delay is chosen to correspond with the drawer closing.
I tried to do the launching onDrawerSlide after 80% progress, but this has two problems. The first was that it stuttered. The second was that if you increased the percentage to 90% or 95% the likelihood that it wouldn't get called at all due to the nature of the animation increased--and you then had to fall back to onDrawerClosed, which defeats the purpose.
This solution has the possibility to stutter, specially on older phones, but the likelihood can be reduced to 0 simply by increasing the delay high enough. I thought 250ms was a reasonable balance between stutter and latency.
The relevant portions of the code look like this:
public class DrawerActivity extends SherlockFragmentActivity {
private final Handler mDrawerHandler = new Handler();
private void scheduleLaunchAndCloseDrawer(final View v) {
// Clears any previously posted runnables, for double clicks
mDrawerHandler.removeCallbacksAndMessages(null);
mDrawerHandler.postDelayed(new Runnable() {
#Override
public void run() {
onDrawerItemSelection(v);
}
}, 250);
// The millisecond delay is arbitrary and was arrived at through trial and error
mDrawerLayout.closeDrawer();
}
}
Google IOsched 2015 runs extremely smoothly (except from settings) the reason for that is how they've implemented the drawer and how they launch stuff.
First of they use handler to launch delayed:
// launch the target Activity after a short delay, to allow the close animation to play
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
goToNavDrawerItem(itemId);
}
}, NAVDRAWER_LAUNCH_DELAY);
with delay being:
private static final int NAVDRAWER_LAUNCH_DELAY = 250;
Another thing they do is to remove animations from the activities that are launched with following code inside the activities onCreate():
overridePendingTransition(0, 0);
To view the source go to git.
I'm using approach like below. Works smoothly.
public class MainActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener {
private DrawerLayout drawerLayout;
private MenuItem menuItemWaiting;
/* other stuff here ... */
private void setupDrawerLayout() {
/* other stuff here ... */
drawerLayout.addDrawerListener(new DrawerLayout.SimpleDrawerListener() {
#Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if(menuItemWaiting != null) {
onNavigationItemSelected(menuItemWaiting);
}
}
});
}
#Override
public boolean onNavigationItemSelected(MenuItem menuItem) {
menuItemWaiting = null;
if(drawerLayout.isDrawerOpen(GravityCompat.START)) {
menuItemWaiting = menuItem;
drawerLayout.closeDrawers();
return false;
};
switch(menuItem.getItemId()) {
case R.id.drawer_action:
startActivity(new Intent(this, SecondActivity.class));
/* other stuff here ... */
}
return true;
}
}
The same with ActionBarDrawerToggle:
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close){
#Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if(menuItemWaiting != null) {
onNavigationItemSelected(menuItemWaiting);
}
}
};
drawerLayout.setDrawerListener(drawerToggle);
A better approach would be to use the onDrawerSlide(View, float) method and start the Activity once the slideOffset is 0. See below
public void onDrawerSlide(View drawerView, float slideOffset) {
if (slideOffset <= 0 && mPendingDrawerIntent != null) {
startActivity(mPendingDrawerIntent);
mPendingDrawerIntent = null;
}
}
Just set the mPendingDrawerIntent in the Drawer's ListView.OnItemClickListener onItemClick method.
This answer is for guys who uses RxJava and RxBinding. Idea is to prevent the activity launch until drawer closes. NavigationView is used for displaying the menu.
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener{
private DrawerLayout drawer;
private CompositeDisposable compositeDisposable;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setup views and listeners (NavigationView.OnNavigationItemSelectedListener)
compositeDisposable = new CompositeDisposable();
compositeDisposable.add(observeDrawerClose());
}
// uncomment if second activitiy comes back to this one again
/*
#Override
protected void onPause() {
super.onPause();
compositeDisposable.clear();
}
#Override
protected void onResume() {
super.onResume();
compositeDisposable.add(observeDrawerClose());
}*/
#Override
protected void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
navSubject.onNext(id);
drawer.closeDrawer(GravityCompat.START);
return true;
}
private Disposable observeDrawerClose() {
return RxDrawerLayout.drawerOpen(drawer, GravityCompat.START)
.skipInitialValue() // this is important otherwise caused to zip with previous drawer event
.filter(open -> !open)
.zipWith(navSubject, new BiFunction<Boolean, Integer, Integer>() {
#Override
public Integer apply(Boolean aBoolean, Integer u) throws Exception {
return u;
}
}).subscribe(id -> {
if (id == R.id.nav_home) {
// Handle the home action
} else {
}
});
}
}
I'm trying to implement a Navigation Drawer that hides the menu items in the ActionBar whenever the drawer is opened.
I am following google's documentation, however their code does not produce the expected behavior.
http://developer.android.com/training/implementing-navigation/nav-drawer.html
Using this code, the menu items are hidden when the drawer becomes completely opened , and shown when the drawer becomes completely closed.
However, the Gmail app behaves differently. The menu items are hidden as soon as the drawer opens by any amount. This is the behavior I want. Does anyone know how to achieve this?
Thanks!
Have you tried this:
Use invalidateOptionsMenu() whenever you toggle the nav drawer, by measuring the sliding offset.
Iterate over each menu item in onPrepareOptionsMenu(Menu menu) and hide it.
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
// If the nav drawer is open, hide action items related to the content view
boolean drawerOpen = shouldGoInvisible;
hideMenuItems(menu, !drawerOpen);
return super.onPrepareOptionsMenu(menu);
}
private void hideMenuItems(Menu menu, boolean visible)
{
for(int i = 0; i < menu.size(); i++){
menu.getItem(i).setVisible(visible);
}
}
Detecting how much the nav drawer has slided:
mDrawerLayout.setDrawerListener(new DrawerListener(){
float mPreviousOffset = 0f;
#Override
public void onDrawerClosed(View arg0) {
super.onDrawerClosed(arg0);
shouldGoInvisible = false;
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
#Override
public void onDrawerOpened(View arg0) {
super.onDrawerOpened(arg0);
shouldGoInvisible = true;
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
#Override
public void onDrawerSlide(View arg0, float slideOffset) {
super.onDrawerSlide(arg0, slideOffset);
if(slideOffset > mPreviousOffset && !shouldGoInvisible){
shouldGoInvisible = true;
invalidateOptionsMenu();
}else if(mPreviousOffset > slideOffset && slideOffset < 0.5f && shouldGoInvisible){
shouldGoInvisible = false;
invalidateOptionsMenu();
}
mPreviousOffset = slideOffset;
}
#Override
public void onDrawerStateChanged(int arg0) {
// or use states of the drawer to hide/show the items
}});
Note: shouldGoInvisible is class field.
If you want to override action bar as soon as drawer enters screen and restore action bar the moment drawer is no longer visible (exactly how Gmail behaves as of March 20, 2014) you can use the following code:
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {
#Override
public void onDrawerStateChanged(int newState) {
super.onDrawerStateChanged(newState);
boolean isOpened = mDrawerLayout.isDrawerOpen(mDrawerList);
boolean isVisible = mDrawerLayout.isDrawerVisible(mDrawerList);
if (!isOpened && !isVisible) {
if (newState == DrawerLayout.STATE_IDLE) {
// drawer just hid completely
restoreActionBar();
} else {
// } else if (newState == DrawerLayout.STATE_SETTLING) {
// drawer just entered screen
overrideActionBar();
}
}
}
private void restoreActionBar() {
getSupportActionBar().setTitle(mTitle);
supportInvalidateOptionsMenu();
}
private void overrideActionBar() {
getSupportActionBar().setTitle(mDrawerTitle);
supportInvalidateOptionsMenu();
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
Modify restoreActionBar() and overrideActionBar() methods acording to your needs.
There is no need to distinguish between swipes and home button and no need to measure swipe lengths.
Variation
If you don't want to reference the drawer list view use the following code instead:
boolean isOpened = mDrawerLayout.isDrawerOpen(GravityCompat.START);
boolean isVisible = mDrawerLayout.isDrawerVisible(GravityCompat.START);
You may want to use GravityCompat.END instead depending on what you specified in XML layout.
Edit - concerning actions
The above example does not hide action bar items relevant to content below the navigation drawer. To do so or show different set of icons when drawer is visible you have to keep track of whether the drawer is opened or closed manually.
In addition to the above code declare private boolean mDrawerVisible = false with proper save/restore state handling.
Then modify mDrawerToggle inner methods as follows:
private void restoreActionBar() {
getSupportActionBar().setTitle(mTitle);
mDrawerVisible = false;
supportInvalidateOptionsMenu();
}
private void overrideActionBar() {
getSupportActionBar().setTitle(mDrawerTitle);
mDrawerVisible = true;
supportInvalidateOptionsMenu();
}
Finally in onCreateOptionsMenu inflate different menu resource or in onPrepareOptionsMenu show/hide different actions based on the value of mDrawerVisible.
I'd half agree with Nikola but it's sufficient just to update the icons when the drawer state has
Create a global variable to keep track of the drawer state:
private int mDrawerState;
Set a new DrawerListener:
mDrawerLayout.setDrawerListener(new DrawerListener() {
#Override
public void onDrawerStateChanged(int state) {
mDrawerState = state;
invalidateOptionsMenu();
}
#Override
public void onDrawerSlide(View view, float slide) {
// TODO Auto-generated method stub
}
#Override
public void onDrawerOpened(View view) {
// TODO Auto-generated method stub
}
#Override
public void onDrawerClosed(View view) {
// TODO Auto-generated method stub
}
});
Update the menu visibility:
boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawer);
for(int i=0;i<menu.size();i++){
// If the drawer is moving / settling or open do not draw the icons
menu.getItem(i).setVisible(mDrawerState!=DrawerLayout.STATE_DRAGGING &&
mDrawerState!=DrawerLayout.STATE_SETTLING && !drawerOpen);
}
I have a better solution for this question:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (navigationDrawerFragment.isDrawerOpen()) {
menu.clear();
}
return super.onPrepareOptionsMenu(menu);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!navigationDrawerFragment.isDrawerOpen()) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
showLocalContextActionBar();
return false;
}
return super.onCreateOptionsMenu(menu);
}
If you want to hide all menu items, simply use:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
return showActionBarMenu; // boolean value, set it in drawer listeners as class variable
}
Then you do not need to visible of each menu item.
I took the answer by #Laurence Dawson and simplified it a bit. This solution does not require any class members to be used.
Execute this code during onCreate():
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
drawerLayout.setDrawerListener(new DrawerLayout.DrawerListener() {
#Override
public void onDrawerSlide(View view, float v) {
invalidateOptionsMenu();
}
#Override
public void onDrawerClosed(View view) {
invalidateOptionsMenu();
}
#Override
public void onDrawerOpened(View view) {}
#Override
public void onDrawerStateChanged(int state) {}
});
And override this method:
#Override
public boolean onPrepareOptionsMenu(Menu menu) {
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
boolean actionsVisibility = !drawerLayout.isDrawerVisible(Gravity.START);
for(int i=0;i<menu.size();i++){
menu.getItem(i).setVisible(actionsVisibility);
}
return super.onPrepareOptionsMenu(menu);
}
Few notes:
The above implementation assumes that the view associated with NavigationDrawer has its layout_gravity set to start in XML.
Unrelated to OP's question, but annoying: there seems to be some kind of a bug which causes the drawer to stuck along the way. If you do observe this behavior, here is the solution: Android Navigation Drawer bug using the sample
I have different code but the same solution:
#Override
public boolean onCreateOptionsMenu(Menu menu) {
hideMenuItems(menu, !mShouldGoInvisible);
return super.onCreateOptionsMenu(menu);
}
private void hideMenuItems(Menu menu, boolean visible){
for(int i = 0; i < menu.size(); i++){
menu.getItem(i).setVisible(visible);
}
}
and
#Override
public void onNavigationDrawerListener(boolean opened, int position) {
if (opened){
mShouldGoInvisible = true;
invalidateOptionsMenu();
} else {
mShouldGoInvisible = false;
invalidateOptionsMenu();
}
}