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 {
}
});
}
}
Related
I have an activity in which I have Tablayout and have two tab named as "Deal","Story" when I navigate to other activity and again resume to that activity
then that tab was appears which I had viewed earlier,while I want whenever I resumed to that activity always show default tab .How can I DO THAT
code:-
public void init() {
s_oCloginSession = new CLoginSessionManagement(CMainActivity.this);// object creation of Login Session...
setupToolbar();// setting toolbar
// navigation bar code
m_Drawer = (DrawerLayout) findViewById(R.id.drawer_layout);//finding id of drawerlayout
s_drawerToggle = new ActionBarDrawerToggle(
this, m_Drawer, m_Toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
m_Drawer.setDrawerListener(s_drawerToggle);
m_Drawer.setScrimColor(getResources().getColor(android.R.color.transparent));
s_drawerToggle.syncState();
NavigationView m_Navigation = (NavigationView) findViewById(R.id.nav_view);
m_Navigation.setNavigationItemSelectedListener(this);
m_TabLayout = (TabLayout) findViewById(R.id.tab_layout);// finding Id of tablayout
m_TabLayout.addTab(m_TabLayout.newTab().setText("Deals"));// add deal listin tab
m_TabLayout.addTab(m_TabLayout.newTab().setText("Stories"));// add stories tab
m_TabLayout.setTabGravity(TabLayout.GRAVITY_FILL);// setting Gravity of Tab
m_ViewPager = (ViewPager) findViewById(R.id.pager);//finding Id of ViewPager
CDealMainListingPager m_oDealMainScreenPager = new CDealMainListingPager
(getSupportFragmentManager(), m_TabLayout.getTabCount());
m_ViewPager.setAdapter(m_oDealMainScreenPager);// adiing adapter to ViewPager
m_ViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(m_TabLayout));// performing action of page changing
m_TabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
m_ViewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
You can create one class named BaseActivity which extends AppCompatActivity. All your activity will now extend to this BaseActivity class instead of AppCompatActivity. So whenever you have not defined onBackPressed it will call it's parent class method and perform it's operations.
public class BaseActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onBackPressed() {
super.onBackPressed();
overridePendingTransition(R.anim.translate, R.anim.left_to_right_simple);
}
}
If you want to customise this method for one class then just need to override this method in that respective class. For other classes it will work same as defined in BaseActivity
Your all activity will be something like this.
public class HomeScreen extends BaseActivity{
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
Make a class, say CommonMethods and put the method into that class:
public class CommonMethods extends AppCompatActivity {
/*This method functions when user press device back button*/
#Override
public void onBackPressed() {
long currentTime = System.currentTimeMillis();
if ((currentTime - lastPressTime) < 2000) {
// Double Press
moveTaskToBack(true);
} else {
Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT).show();
lastPressTime = currentTime;
}
}
}
Here, you can make this class extend Activity or AppCompatActivity based on your needs.
Finally, you can make the activity classes which need this feature, extend this CommonMethods class.
You can do this by taking one common class and implement this function in it,
Check below code,
Take one common class for method
//Here I am taking it as common class you can take it as per your choice
CommonClass.java
public class CommonClass
{
public bool manageBackPressed(Activity activity, long lastPressTime)
{
long currentTime = System.currentTimeMillis();
if ((currentTime - lastPressTime) < 2000) {
return true;
} else {
return false;
}
return false;
}
}
Now below check your onBackPressed method in any activity
#Override
public void onBackPressed() {
if(commonClass.manageBackPressed(this, lastPressTime))
{
moveTaskToBack(true);
}
else
{
Toast.makeText(this, "Press again to exit", Toast.LENGTH_SHORT).show();
lastPressTime = System.currentTimeMillis();
}
}
I think your previous thread will also work in background please cancel it when you dont want to update.
I'm currently using within my app the android.support.v7.widget.Toolbar and android.support.v4.widget.DrawerLayout. Everything is working right, but there is a slight thing I want to change its behaviour.
When I open the drawer, the whole drawer occupies the space of the Toolbar. It would be nice that the Toolbar stays on top, like in the Google Music app. How can I achieve that?
But the most important thing isn't the previous. At first, the icon which is loaded in the application is the three stripped one. I've realised that after opening the drawer, the icon changes to an arrow. And after loading a fragment, the arrow remains there as the Toolbar icon, even if I press back until the first screen. How could I avoid the arrow appearing from after opening the drawer? I'd want to change this icon manually, specially when I load lower level fragments.
Thanks for your help!
Code:
public class HomeActivity extends BaseActivity {
...
private DrawerLayout mDrawer;
private ActionBarDrawerToggle mDrawerToggle;
private ListView mDrawerList;
private ListView mDrawerRightList;
private RelativeLayout mDrawerRelativeLayout;
private String[] mDrawerMenuTitles;
private Toolbar mToolbar;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDrawerMenuTitles = getResources().getStringArray(R.array.main_menu_options);
mDrawer = (DrawerLayout) findViewById(R.id.drawer);
mDrawerRelativeLayout = (RelativeLayout) findViewById(R.id.theDrawerRelativeLayout);
mDrawer.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
mDrawerRightList = (ListView) findViewById(R.id.theDrawerRight);
mDrawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerRightList);
mDrawerList = (ListView) findViewById(R.id.theDrawer);
mDrawerList.setAdapter(new ArrayAdapter<String>(
getSupportActionBar().getThemedContext(),
R.layout.drawer_list_item,
mDrawerMenuTitles
));
mDrawerList.setOnItemClickListener(new DrawerItemClickListener());
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
setmToolbar((Toolbar) findViewById(R.id.toolbar));
mDrawerToggle = new ActionBarDrawerToggle(
this,
mDrawer,
mToolbar,
R.string.drawer_open,
R.string.drawer_close){
#Override
public void onDrawerSlide(View drawerView, float slideOffset) {
// TODO Auto-generated method stub
super.onDrawerSlide(drawerView, slideOffset);
}
#Override
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
//getSupportActionBar().setTitle(CURRENT_FRAGMENT);
}
/** Called when a drawer has settled in a completely open state. */
#Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
//getSupportActionBar().setTitle("Configuración");
}
};
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerToggle.syncState();
mDrawer.setDrawerListener(mDrawerToggle);
setToolbarSubtitle(getString(R.string.misrutas_titulo));
initialisePreferences(savedInstanceState);
if (savedInstanceState == null) {
replaceFragment(DEFAULT_FRAGMENT);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
mDrawer.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
private class DrawerItemClickListener implements OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//selectItem(position);
final int thePos = position;
mDrawer.setDrawerListener( new DrawerLayout.SimpleDrawerListener(){
#Override
public void onDrawerClosed(View drawerView){
....
}
});
if(mDrawer.isDrawerOpen(GravityCompat.START))
mDrawer.closeDrawer(mDrawerRelativeLayout);
}
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggls
mDrawerToggle.onConfigurationChanged(newConfig);
}
#Override
public void onBackPressed(){
setToolbarSubtitle(getString(R.string.app_name_subtitle));
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
finish();
}
else {
super.onBackPressed();
}
}
private void replaceFragment (String to){
if(!to.equalsIgnoreCase(CURRENT_FRAGMENT)){
CURRENT_FRAGMENT = to;
Fragment fragment = Fragment.instantiate(HomeActivity.this, to);
String backStateName = fragment.getClass().getName();
String fragmentTag = backStateName;
FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);
if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content_frame, fragment, fragmentTag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();
}
}
}
//More methods
....
}
EDIT:
I have been able to modify a little bit my layout, so that the Toolbar stays on top of the rest screen (drawer and fragment). But I haven't been able to control the icon. When I open the drawer, the hamburger icon converts into the arrow. When I close the drawer, the arrow is converted into the hamburger. But when I press an option within the drawer, a new fragment is inflated and the hamburger icon is replaced by the arrow until the application is closed, so that the hamburger icon is never seen.
How can I adapt the icon behaviour so that it doesn't change from hamburger to arrow when I click over a drawer option?
now I try to do a similiar navigation drawer as you...
Basically respect me is that I had a diferent package for navigation drawer that I think so is different from your navigation drawer... My package is:
//From android studio
compile 'com.android.support:appcompat-v7:21.0.0'
I don't know if this can help you, but I follow this greatest tutorial and my navigation drawer work it's similar as you wish!
http://androideity.com/2013/12/16/android-navigation-drawer-parte-1/
http://androideity.com/2014/02/26/android-navigation-drawer-parte-2/
I wait that I help you! Good luck!
PD: If you have a problems with this tutorial or same advice me! :D
At first glance seems that you are setting new drawer listener whenever item is selected, precisely in this snippet:
private class DrawerItemClickListener implements OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//selectItem(position);
final int thePos = position;
mDrawer.setDrawerListener( new DrawerLayout.SimpleDrawerListener(){
#Override
public void onDrawerClosed(View drawerView){
....
}
});
if(mDrawer.isDrawerOpen(GravityCompat.START))
mDrawer.closeDrawer(mDrawerRelativeLayout);
}
}
This snippet replaces ActionBarDrawerToggle (which implements DrawerListener as well), so no calls are made to ActionBarDrawerToggle which controls the icon.
How can I adapt the icon behaviour so that it doesn't change from hamburger to arrow when I click over a drawer option?
Relating to your previous statements, this sound vague to me. Why you'd be preventing this default behaviour? You can prevent morphing by not calling super calls in DrawerListener's methods.
I've implemented the newest appcompat library and using the Toolbar as action bar. But the problem is I cannot catch the home button / hamburger icon click event. I've tried and looked everything but doesn't seem to find a similar problem.
This is my Activity class :
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Set up the drawer.
navDrawerFragment =
(NavigationDrawerFragment) getSupportFragmentManager()
.findFragmentById(R.id.navigation_drawer);
navDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout),
toolbar);
}
And this is my NavigationDrawerFragment class :
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
currentSelectedPosition = savedInstanceState.getInt(
STATE_SELECTED_POSITION);
fromSavedInstanceState = true;
}
// Select either the default item (0) or the last selected item.
selectItem(currentSelectedPosition);
}
#Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Indicate that this fragment would like
// to influence the set of actions in the action bar.
setHasOptionsMenu(true);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
drawerListView = (ListView) inflater.inflate(
R.layout.fragment_navigation_drawer, container, false);
drawerListView.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent,
View view, int position, long id) {
selectItem(position);
}
});
//mDrawerListView.setAdapter();
//mDrawerListView.setItemChecked(mCurrentSelectedPosition, true);
return drawerListView;
}
public void setUp(int fragmentId, DrawerLayout drawerLayout, Toolbar toolbar) {
fragmentContainerView = getActivity().findViewById(fragmentId);
this.drawerLayout = drawerLayout;
// set a custom shadow that overlays the main
// content when the drawer opens
drawerLayout.setDrawerShadow(
R.drawable.drawer_shadow, GravityCompat.START);
// set up the drawer's list view
// with items and click listener
ActionBar actionBar = getActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
// ActionBarDrawerToggle ties together the the proper interactions
// between the navigation drawer and the action bar app icon.
drawerToggle = new ActionBarDrawerToggle(
getActivity(),
drawerLayout,
toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close) {
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
}
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
}
};
// If the user hasn't 'learned' about the drawer,
// open it to introduce them to the drawer,
// per the navigation drawer design guidelines.
if (!userLearnedDrawer && !fromSavedInstanceState) {
drawerLayout.openDrawer(fragmentContainerView);
}
// Defer code dependent on restoration of previous instance state.
drawerLayout.post(new Runnable() {
#Override
public void run() {
drawerToggle.syncState();
}
});
drawerLayout.setDrawerListener(drawerToggle);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_SELECTED_POSITION, currentSelectedPosition);
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Forward the new configuration the drawer toggle component.
drawerToggle.onConfigurationChanged(newConfig);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d("cek", "item selected");
if (drawerToggle.onOptionsItemSelected(item)) {
Log.d("cek", "home selected");
return true;
}
return super.onOptionsItemSelected(item);
}
when I clicked a menu item, the log "item selected" gets called. But when I click on the home button, it opens navigation drawer but the log "home selected" never get called. I've set onOptionsItemSelected method inside my Activity as well, but it still doesn't get called.
If you want to know when home is clicked is an AppCompatActivity then you should try it like this:
First tell Android you want to use your Toolbar as your ActionBar:
setSupportActionBar(toolbar);
Then set Home to be displayed via setDisplayShowHomeEnabled like this:
getSupportActionBar().setDisplayShowHomeEnabled(true);
Finally listen for click events on android.R.id.home like usual:
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == android.R.id.home) {
Timber.d("Home pressed");
}
return super.onOptionsItemSelected(menuItem);
}
If you want to know when the navigation button is clicked on a Toolbar in a class other than AppCompatActivity you can use these methods to set a navigation icon and listen for click events on it. The navigation icon will appear on the left side of your Toolbar where the the "home" button used to be.
toolbar.setNavigationIcon(getResources().getDrawable(R.drawable.ic_nav_back));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d("cek", "home selected");
}
});
If you want to know when the hamburger is clicked and when the drawer opens, you're already listening for these events via onDrawerOpened and onDrawerClosed so you'll want to see if those callbacks fit your requirements.
mActionBarDrawerToggle = mNavigationDrawerFragment.getActionBarDrawerToggle();
mActionBarDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// event when click home button
}
});
in mycase this code work perfect
This is how I do it to return to the right fragment otherwise if you have several fragments on the same level it would return to the first one if you don´t override the toolbar back button behavior.
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
I think the correct solution with support library 21 is the following
// action_bar is def resource of appcompat;
// if you have not provided your own toolbar I mean
Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
if (toolbar != null) {
// change home icon if you wish
toolbar.setLogo(this.getResValues().homeIconDrawable());
toolbar.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//catch here title and home icon click
}
});
}
I have handled back and Home button in Navigation Drawer like
public class HomeActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
private ActionBarDrawerToggle drawerToggle;
private DrawerLayout drawerLayout;
NavigationView navigationView;
private Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
resetActionBar();
navigationView = (NavigationView) findViewById(R.id.navigation_view);
navigationView.setNavigationItemSelectedListener(this);
//showing first fragment on Start
getSupportFragmentManager().beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN).replace(R.id.content_fragment, new FirstFragment()).commit();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
//listener for home
if(id==android.R.id.home)
{
if (getSupportFragmentManager().getBackStackEntryCount() > 0)
onBackPressed();
else
drawerLayout.openDrawer(navigationView);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START))
drawerLayout.closeDrawer(GravityCompat.START);
else
super.onBackPressed();
}
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Begin the transaction
Fragment fragment = null;
// Handle navigation view item clicks here.
int id = item.getItemId();
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (id == R.id.nav_companies_list) {
fragment = new FirstFragment();
// Handle the action
}
// Begin the transaction
if(fragment!=null){
if(item.isChecked()){
if(getSupportFragmentManager().getBackStackEntryCount()==0){
drawer.closeDrawers();
}else{
removeAllFragments();
getSupportFragmentManager().beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE).replace(R.id.WikiCompany, fragment).commit();
drawer.closeDrawer(GravityCompat.START);
}
}else{
removeAllFragments();
getSupportFragmentManager().beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE).replace(R.id.WikiCompany, fragment).commit();
drawer.closeDrawer(GravityCompat.START);
}
}
return true;
}
public void removeAllFragments(){
getSupportFragmentManager().popBackStackImmediate(null,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
public void replaceFragment(final Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.replace(R.id.WikiCompany, fragment).addToBackStack("")
.commit();
}
public void updateDrawerIcon() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
try {
Log.i("", "BackStackCount: " + getSupportFragmentManager().getBackStackEntryCount());
if (getSupportFragmentManager().getBackStackEntryCount() > 0)
drawerToggle.setDrawerIndicatorEnabled(false);
else
drawerToggle.setDrawerIndicatorEnabled(true);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}, 50);
}
public void resetActionBar()
{
//display home
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
public void setActionBarTitle(String title) {
getSupportActionBar().setTitle(title);
}
}
and In each onViewCreated I call
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
((HomeActivity)getActivity()).updateDrawerIcon();
((HomeActivity) getActivity()).setActionBarTitle("List");
}
This is how I implemented it pre-material design and it seems to still work now I've switched to the new Toolbar. In my case I want to log the user in if they attempt to open the side nav while logged out, (and catch the event so the side nav won't open). In your case you could not return true;.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (!isLoggedIn() && item.getItemId() == android.R.id.home) {
login();
return true;
}
return mDrawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
}
I changed the DrawerLayout a bit to get the events and be able to consume and event, such as if you want to use the actionToggle as back if you are in detail view:
public class ListenableDrawerLayout extends DrawerLayout {
private OnToggleButtonClickedListener mOnToggleButtonClickedListener;
private boolean mManualCall;
public ListenableDrawerLayout(Context context) {
super(context);
}
public ListenableDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ListenableDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the listener for the toggle button
*
* #param mOnToggleButtonClickedListener
*/
public void setOnToggleButtonClickedListener(OnToggleButtonClickedListener mOnToggleButtonClickedListener) {
this.mOnToggleButtonClickedListener = mOnToggleButtonClickedListener;
}
/**
* Opens the navigation drawer manually from code<br>
* <b>NOTE: </b>Use this function instead of the normal openDrawer method
*
* #param drawerView
*/
public void openDrawerManual(View drawerView) {
mManualCall = true;
openDrawer(drawerView);
}
/**
* Closes the navigation drawer manually from code<br>
* <b>NOTE: </b>Use this function instead of the normal closeDrawer method
*
* #param drawerView
*/
public void closeDrawerManual(View drawerView) {
mManualCall = true;
closeDrawer(drawerView);
}
#Override
public void openDrawer(View drawerView) {
// Check for listener and for not manual open
if (!mManualCall && mOnToggleButtonClickedListener != null) {
// Notify the listener and behave on its reaction
if (mOnToggleButtonClickedListener.toggleOpenDrawer()) {
return;
}
}
// Manual call done
mManualCall = false;
// Let the drawer layout to its stuff
super.openDrawer(drawerView);
}
#Override
public void closeDrawer(View drawerView) {
// Check for listener and for not manual close
if (!mManualCall && mOnToggleButtonClickedListener != null) {
// Notify the listener and behave on its reaction
if (mOnToggleButtonClickedListener.toggleCloseDrawer()) {
return;
}
}
// Manual call done
mManualCall = false;
// Let the drawer layout to its stuff
super.closeDrawer(drawerView);
}
/**
* Interface for toggle button callbacks
*/
public static interface OnToggleButtonClickedListener {
/**
* The ActionBarDrawerToggle has been pressed in order to open the drawer
*
* #return true if we want to consume the event, false if we want the normal behaviour
*/
public boolean toggleOpenDrawer();
/**
* The ActionBarDrawerToggle has been pressed in order to close the drawer
*
* #return true if we want to consume the event, false if we want the normal behaviour
*/
public boolean toggleCloseDrawer();
}
}
The easiest approach we could do is change the home icon to a known icon and compare drawables (because android.R.id.home icon can differ to different api versions
so set a toolbar as actionbar
SetSupportActionBar(_toolbar);
_toolbar.NavigationIcon = your_known_drawable_here;
for (int i = 0; i < _toolbar.ChildCount; i++)
{
View v = _toolbar.GetChildAt(i);
if (v is ImageButton)
{
ImageButton imageButton = v as ImageButton;
if (imageButton.Drawable.GetConstantState().Equals(_bookMarkIcon.GetConstantState()))
{
//here v is the widget that contains the home icon you can add your click events here
}
}
}
In my case I had to put the icon using:
toolbar.setNavigationIcon(R.drawable.ic_my_home);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
And then listen to click events with default onOptionsItemSelected and android.R.id.home id
For anyone looking for a Xamarin implementation (since events are done differently in C#), I simply created this NavClickHandler class as follows:
public class NavClickHandler : Java.Lang.Object, View.IOnClickListener
{
private Activity mActivity;
public NavClickHandler(Activity activity)
{
this.mActivity = activity;
}
public void OnClick(View v)
{
DrawerLayout drawer = (DrawerLayout)mActivity.FindViewById(Resource.Id.drawer_layout);
if (drawer.IsDrawerOpen(GravityCompat.Start))
{
drawer.CloseDrawer(GravityCompat.Start);
}
else
{
drawer.OpenDrawer(GravityCompat.Start);
}
}
}
Then, assigned a custom hamburger menu button like this:
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.SetDefaultDisplayHomeAsUpEnabled(false);
this.drawerToggle.DrawerIndicatorEnabled = false;
this.drawerToggle.SetHomeAsUpIndicator(Resource.Drawable.MenuButton);
And finally, assigned the drawer menu toggler a ToolbarNavigationClickListener of the class type I created earlier:
this.drawerToggle.ToolbarNavigationClickListener = new NavClickHandler(this);
And then you've got a custom menu button, with click events handled.
Try this code
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == android.R.id.home){
//You can get
}
return super.onOptionsItemSelected(item);
}
Add below code to your onCreate() metod
ActionBar ab = getSupportActionBar();
ab.setDisplayHomeAsUpEnabled(true);
Apart from the answer provided by MrEngineer13, there is also another possible reason why the click event might not have been captured in the onOptionsSelected method. Your DrawerLayout may have overlayed your Toolbar's interface component in the layout XML file. Therefore, whenever you attempt to click the Home button, you're only clicking the DrawerLayout, but not the Home button that's located beneath it.
All you have to do now is rearrange your Toolbar in the corresponding layout XML file so that it is not blocked by any other UI component.
Programmatically, I did attempt to call the bringToFront() method on the toolbar (toolbar.bringToFront()). However, in my app's context, it does not seem to be the solution.
Background:
I have a FragmentActivity that uses a DrawerLayout. I created a class called NavDrawerManager to abstract out the code for dealing with the drawer layout. To construct this object, I need to pass in and keep a reference to an Activity. I use this activity reference to call findViewById(), and I also use the activity as a context when creating a list adapter, etc. I keep a reference to a NavDrawerManager object in my activity so I can perform callbacks and operations on the drawer layout.
UPDATE: Per Xaver's suggestion, I switched to extend FragmentActivity into a NavDrawerActivity. Instead of having a NavDrawerManager object in my activity, the superclass handles the nav drawer code. See updated code.
Issue:
Everything works fine until I change orientations. After changing orientations, it appears that my NavDrawerActivity still references the old layout. I say this because I have a progress bar and a couple of buttons in the DrawerLayout that I am attempting to update, but they don't reflect the updates.
When I call Activity.findViewById() to get the progress bar and make it visible, it doesn't show any changes, nor do any of the changes to the buttons show. Note: Activity.findViewById() does not return null. However, if I do the same things before changing orientation, the progress bar and buttons show the appropriate changes.
Code:
NavDrawerActivity:
public class NavDrawerActivity extends LowProfileFragmentActivity {
private DrawerLayout mDrawerLayout;
private ActionBarDrawerToggle mDrawerToggle;
private ExpandableListView mDrawerList;
private NavDrawerAdapter mListAdapter;
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
mDrawerToggle.syncState();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
mDrawerToggle.onOptionsItemSelected(item);
return super.onOptionsItemSelected(item);
}
//Called via subclass after setContentView
protected void setupNavDrawer() {
initDrawerLayout();
initDrawerList();
setButton1();
setButton2();
}
private void initDrawerLayout() {
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = getActionBarDrawerToggle();
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
private void initDrawerList() {
mDrawerList = (ExpandableListView) mDrawerLayout
.findViewById(android.R.id.list);
mListAdapter = getNavDrawerAdapter();
}
private ActionBarDrawerToggle getActionBarDrawerToggle() {
return new ActionBarDrawerToggle(this, mDrawerLayout,
R.drawable.ic_navigation_drawer, 0, 0) {
public void onDrawerClosed(View view) {
}
public void onDrawerOpened(View view) {
}
};
}
public void setDrawerLoading(boolean loading) {
ProgressBar progressBar = (ProgressBar) mDrawerLayout
.findViewById(R.id.progress_bar);
Button button1 = (Button) this.findViewById(R.id.button1);
Button button2 = (Button) this
.findViewById(R.id.button2);
/* None of the below changes appear after changing orientation */
if (loading) {
button1.setEnabled(false);
button2.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
button1.setEnabled(true);
button2.setEnabled(true);
}
}
private void setButton1() {
Button button1 = (Button) this.findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//do something
}
});
}
private void setButton2() {
Button button2 = (Button) this
.findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//do something
}
});
}
}
ExampleActivity:
public class ExampleActivity extends NavDrawerActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initUI();
}
private void initUI() {
setContentView(R.layout.activity_sift);
// Call the superclass method to set up the nav drawer
setupNavDrawer();
}
}
AndroidManifest.xml:
<activity
android:name="com.example.app.ExampleActivity"
android:label="#string/app_name"
android:launchMode="singleTop"
android:theme="#style/AppTheme" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Wouldn't it be easier if you implemented a NavDrawerActivity instead of a NavDrawerManager? Generally things like this should be avoided. The UI should be handled solely by the Fragments or Activities themselves.
I understand why you would want to create such a NavDrawerManager, simply to make the whole thing reusable and a lot of abstraction is great, but when it is overdone it can become a problem. Don't focus so much on everything being perfectly abstracted and according to OOP standards. There is nothing wrong with an Activity having both a NavigationDrawer and hiding the notification bar. And if you already have an Activity that implements one of those two things you should extend it. Your only other option would be to reimplement the same behaviour in a second Activity and that would pretty much defeat the purpose of abstraction.
EDIT: I have refactored and improved your code, you really should not have so many different methods for everything, all the setup belongs in onCreate(), that you write extra methods for each step just introduces the possibility for additional errors. Try this:
public abstract class NavDrawerActivity extends LowProfileFragmentActivity {
private ActionBarDrawerToggle drawerToggle;
private DrawerLayout drawerLayout;
private ProgressBar progressBar;
private Button button1;
private Button button2;
private ExpandableListView drawerList;
private NavDrawerAdapter drawerListAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
this.drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
this.drawerToggle = new ActionBarDrawerToggle(this, this.drawerLayout, R.drawable.icon_drawer, R.string.drawer_open, R.string.drawer_close);
this.drawerLayout.setDrawerListener(this.drawerToggle);
this.drawerList = (ExpandableListView) this.drawerLayout.findViewById(android.R.id.list);
this.progressBar = (ProgressBar) this.drawerLayout.findViewById(R.id.progress_bar);
this.button1 = (Button) findViewById(R.id.button1);
this.button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//do something
}
});
this.button2 = (Button) findViewById(R.id.button2);
this.button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//do something
}
});
this.drawerListAdapter = new NavDrawerAdapter(...);
this.drawerList.setAdapter(this.drawerListAdapter);
}
protected abstract int getLayout();
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
this.drawerToggle.syncState();
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
this.drawerToggle.onConfigurationChanged(newConfig);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return this.drawerToggle.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
}
public void setDrawerLoading(boolean loading) {
this.button1.setEnabled(!loading);
this.button2.setEnabled(!loading);
this.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
}
}
As you can see I use an abstract method called getLayout() to pass the correct layout to setContentView(). getLayout() has to be implemented by all Activities which extend the NavDrawerActivity therfore giving the sub Activities control over the used layout.
As such your ExampleActivity should look like this:
public class ExampleActivity extends NavDrawerActivity {
#Override
protected int getLayout() {
return R.layout.activity_sift;
}
}
I tested everything and it's working for me.
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);
}