Is it possible to use a navigation drawer in android but instead of updating fragments, i would like to switch between activities as my means of navigation within the app.
Yes it is possible - it's what I did for my app. I already had a number of activities set up, and rather than convert them all to fragments, I wanted to tailor the navigation drawer to work across all of them. Unfortunately, it's not a quick workaround, so if you have the option of using fragments, I would go with that. But regardless here's how I did it:
Let's say I have 2 activities, both of which I want to have the Navigation Drawer. In the layout.xml for each, I specified a DrawerLayout with the appropriate ListView to hold my navigation options. Essentially, the Navigation drawer is made every time I switch between activities, giving the appearance that it is persisting. To make life a lot easier, I took the common methods required to set up the navigation drawer and put them in their own class: NavigationDrawerSetup.java. That way my activities can use the same custom adapter, etc.
Within this NavigationDrawerSetup.java class, I have the following:
configureDrawer() - this sets up the ActionBar,
ActionBarDrawerToggle, and the required listeners
My custom array adapter (to populate the navigation options within the list)
The selectOptions() method, which handles drawer item clicks
When you set up the navigation drawer within one of your activities, you just create a new NavigationDrawerSetup object and pass in the required layout parameters (like the DrawerLayout, ListView etc). Then you'd call configureDrawer():
navigationDrawer = new NavigationDrawerSetup(mDrawerView, mDrawerLayout,
mDrawerList, actionBar, mNavOptions, currentActivity);
navigationDrawer.configureDrawer();
currentActivity is passed in since the navigation drawer is tied to the activity you are on. You will have to use it when you set up the ActionBarDrawerToggle:
mDrawerToggle = new ActionBarDrawerToggle(currentActivity, // host Activity
mDrawerLayout, /* DrawerLayout object */
R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */
R.string.drawer_open, /* "open drawer" description for accessibility */
R.string.drawer_close /* "close drawer" description for accessibility */
)
You will also need to use currentActivity when setting up your custom Adapter:
As for how to switch between activities via the navigation drawer, you can just set up new intents within your selectItem() method:
private void selectItem(int position) {
// Handle Navigation Options
Intent intent;
switch (position) {
case 0:
intent = new Intent(currentActivity, NewActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
currentActivity.startActivity(intent);
break;
case 1:
// etc.
}
Just make sure that your new Activity also has the navigation drawer setup and it should display.
There are a ton of things you can do to customize this method to your own needs, but this is the general structure of how I did it. Hope this helps!
You need a BaseDrawerActivity which implement the Navigation Drawer then extend the BaseDrawerActivity in each activity you need Navigation Drawer.
First create BaseDrawerActivity.java :
public class BaseDrawerActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener{
DrawerLayout drawerLayout;
Toolbar toolbar;
FrameLayout frameLayout;
NavigationView navigationView;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_base_drawer);;
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
frameLayout = (FrameLayout) findViewById(R.id.content_frame);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.setDrawerListener(toggle);
toggle.syncState();
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
}
#Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
#Override
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
//to prevent current item select over and over
if (item.isChecked()){
drawerLayout.closeDrawer(GravityCompat.START);
return false;
}
if (id == R.id.nav_camera) {
// Handle the camera action
startActivity(new Intent(getApplicationContext(), CameraActivity.class));
} else if (id == R.id.nav_gallery) {
startActivity(new Intent(getApplicationContext(), GalleryActivity.class));
} else if (id == R.id.nav_slideshow) {
startActivity(new Intent(getApplicationContext(), SlideshowActivity.class));
} else if (id == R.id.nav_manage) {
startActivity(new Intent(getApplicationContext(), ManageActivity.class));
} else if (id == R.id.nav_share) {
startActivity(new Intent(getApplicationContext(), ShareActivity.class));
} else if (id == R.id.nav_send) {
startActivity(new Intent(getApplicationContext(), SendActivity.class));
}
drawerLayout.closeDrawer(GravityCompat.START);
return true;
}
}
then create activity_base_drawer.xml in res/layout folder:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="start">
<include layout="#layout/app_bar_home"/>
<android.support.design.widget.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="#layout/nav_header_home"
app:menu="#menu/activity_home_drawer" />
</android.support.v4.widget.DrawerLayout>
where #layout/app_bar_home is:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<FrameLayout android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.CoordinatorLayout>
Next you enter your Activities that will have Navigation Drawer such as CameraActivity.java :
public class CameraActivity extends BaseDrawerActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLayoutInflater().inflate(R.layout.activity_camera, frameLayout);
/**
* Setting title
*/
setTitle("Camera");
}
#Override
protected void onResume() {
super.onResume();
// to check current activity in the navigation drawer
navigationView.getMenu().getItem(0).setChecked(true);
}
}
Where R.layout.activity_camera is your layout for CameraActivity.java.
Then create other Activity like GalleryActivity.java and so on that will have Navigation Drawer:
public class GalleryActivity extends BaseDrawerActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLayoutInflater().inflate(R.layout.activity_gallery, frameLayout);
// Setting title
setTitle("Gallery");
}
#Override
protected void onResume() {
super.onResume();
navigationView.getMenu().getItem(1).setChecked(true);
}
}
As a little improvement to the solution pointed by #David-Crozier, in order to avoid the overlap of both animations (closing the NavigationDrawer and starting a new activity), you can include a little delay in your method as was done in the iosched app v2014:
private void onNavDrawerItemClicked(final int itemId) {
if (itemId == getSelfNavDrawerItem()) {
mDrawerLayout.closeDrawer(GravityCompat.START);
return;
}
if (isSpecialItem(itemId)) {
goToNavDrawerItem(itemId);
} else {
// 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);
// change the active item on the list so the user can see the item changed
setSelectedNavDrawerItem(itemId);
// fade out the main content
View mainContent = findViewById(R.id.main_content);
if (mainContent != null) {
mainContent.animate().alpha(0).setDuration(MAIN_CONTENT_FADEOUT_DURATION);
}
}
mDrawerLayout.closeDrawer(GravityCompat.START);
}
Here the link for reference: https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/BaseActivity.java
The Fragment manager can be replaced as mentioned in the post:
https://guides.codepath.com/android/fragment-navigation-drawer#alternative-to-fragments
You can inflate a layout instead of using a fragment manager.
In your mobile_navigation file XML.
Add the following.
<activity
android:id="#+id/nav_custom"
android:name="net.larntech.nav.CustomActivity"
android:label="#string/menu_custom"
tools:layout="#layout/activity_custom"
/>
You can find the full tutorial using the link below.
Android Studio Navigation Drawer
Related
I am using a single activity and multiple fragments(screenshot attached) within the same activity to provide a seamless navigation. But after implementing the latest toolbar and navigation view, it seems hard to handle the navigation and home buttons. I am having trouble with the following things.
Managing the Hamburger/Back button at left top. Toggling the icon and functionality to Menu and Back nav.
Page title - Changing the page titles whenever a fragment in pushed and popped.
I have tried several things like overriding onBackPressed(), setHomeAsUpIndicator, popping fragments manually. Earlier i was using ActionBarDrawer toggle to handle this but it is failing somehow now. I checked the google samples they seem to use separate activities at most of the places.
Can anyone guide me how to implement a proper back navigation to handle the NavigationView, Back button in inner fragments and page titles? I am using AppCompatActivity, android.app.Fragment, NavigationView and Toolbar.
It's much easier to illustrate with some sort of division of responsibility for your Activity and Fragment.
Problem 1: Managing the Hamburger/Back button at left top. Toggling the icon and functionality to Menu and Back nav.
From the illustration, the solution should be encapsulated by the Activity, which will look something like this:
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private ActionBarDrawerToggle mDrawerToggle;
private DrawerLayout mDrawer;
private ActionBar mActionBar;
private boolean mToolBarNavigationListenerIsRegistered = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mActionBar = getSupportActionBar();
mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
mDrawer.addDrawerListener(mDrawerToggle);
mDrawerToggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// On orientation change savedInstanceState will not be null.
// Use this to show hamburger or up icon based on fragment back stack.
if(savedInstanceState != null){
resolveUpButtonWithFragmentStack();
} else {
// You probably want to add your ListFragment here.
}
}
#Override
public void onBackPressed() {
if (mDrawer.isDrawerOpen(GravityCompat.START)) {
mDrawer.closeDrawer(GravityCompat.START);
} else {
int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
if (backStackCount >= 1) {
getSupportFragmentManager().popBackStack();
// Change to hamburger icon if at bottom of stack
if(backStackCount == 1){
showUpButton(false);
}
} else {
super.onBackPressed();
}
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#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;
} else if (id == android.R.id.home) {
// Home/Up logic handled by onBackPressed implementation
onBackPressed();
}
return super.onOptionsItemSelected(item);
}
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
// Navigation drawer item selection logic goes here
mDrawer.closeDrawer(GravityCompat.START);
return true;
}
private void replaceFragment() {
/**
* Your fragment replacement logic goes here
* e.g.
* FragmentTransaction ft = getFragmentManager().beginTransaction();
* String tag = "MyFragment";
* ft.replace(R.id.content, MyFragment.newInstance(tag), tag).addToBackStack(null).commit();
*/
// The part that changes the hamburger icon to the up icon
showUpButton(true);
}
private void resolveUpButtonWithFragmentStack() {
showUpButton(getSupportFragmentManager().getBackStackEntryCount() > 0);
}
private void showUpButton(boolean show) {
// To keep states of ActionBar and ActionBarDrawerToggle synchronized,
// when you enable on one, you disable on the other.
// And as you may notice, the order for this operation is disable first, then enable - VERY VERY IMPORTANT.
if(show) {
// Remove hamburger
mDrawerToggle.setDrawerIndicatorEnabled(false);
// Show back button
mActionBar.setDisplayHomeAsUpEnabled(true);
// when DrawerToggle is disabled i.e. setDrawerIndicatorEnabled(false), navigation icon
// clicks are disabled i.e. the UP button will not work.
// We need to add a listener, as in below, so DrawerToggle will forward
// click events to this listener.
if(!mToolBarNavigationListenerIsRegistered) {
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onBackPressed();
}
});
mToolBarNavigationListenerIsRegistered = true;
}
} else {
// Remove back button
mActionBar.setDisplayHomeAsUpEnabled(false);
// Show hamburger
mDrawerToggle.setDrawerIndicatorEnabled(true);
// Remove the/any drawer toggle listener
mDrawerToggle.setToolbarNavigationClickListener(null);
mToolBarNavigationListenerIsRegistered = false;
}
// So, one may think "Hmm why not simplify to:
// .....
// getSupportActionBar().setDisplayHomeAsUpEnabled(enable);
// mDrawer.setDrawerIndicatorEnabled(!enable);
// ......
// To re-iterate, the order in which you enable and disable views IS important #dontSimplify.
}
}
Problem 2: Page title - Changing the page titles whenever a fragment in pushed and popped.
Essentially, this can be handled in the onStart for each Fragment i.e. your ListFragment, DetailsFragment and CommentsFragment look something like this:
#Override
public void onStart() {
super.onStart();
// where mText is the title you want on your toolbar/actionBar
getActivity().setTitle(mText);
}
Probably worth having setRetainInstance(true) in the onCreate of your fragments as well.
tl;dr
Watch this:
https://youtu.be/ANpBWIT3vlU
Clone this:
https://github.com/shredderskelton/androidtemplate.
This is a really common problem and one that I've overcome by creating a kind of template project which I use whenever I start a new Android project. The idea is to abstract as much of the logic that handles the back button, the 'hamburger' indicator and fragment management into reusable classes:
Start by creating a BaseActivity and BaseFragment class. This is where you are going to as much of the reusable code as possible.
Lets start with your BaseActivity
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentManager = getSupportFragmentManager();
fragmentHandler = new AddFragmentHandler(fragmentManager);
fragmentManager.addOnBackStackChangedListener(backStackListener);
}
The FragmentManager is the key to owning the back stack, so you need to listen for changes to the back stack from here. The AddFramentHandler is a little class I cooked up to make it easier to add Fragments, from Fragments. More on that later.
#Override
public void onBackPressed() {
if (sendBackPressToDrawer()) {
//the drawer consumed the backpress
return;
}
if (sendBackPressToFragmentOnTop()) {
// fragment on top consumed the back press
return;
}
//let the android system handle the back press, usually by popping the fragment
super.onBackPressed();
//close the activity if back is pressed on the root fragment
if (fragmentManager.getBackStackEntryCount() == 0) {
finish();
}
}
onBackPressed is where most of the magic happens. You notice the plain text formatting of the methods.. I'm a huge Clean Code fan - if you need to write comments, your code isn't clean. Basically you need to really have a central place where you can run to when you're not sure why a back button press is not happening the way you expect. This method is that place.
private void syncDrawerToggleState() {
ActionBarDrawerToggle drawerToggle = getDrawerToggle();
if (getDrawerToggle() == null) {
return;
}
if (fragmentManager.getBackStackEntryCount() > 1) {
drawerToggle.setDrawerIndicatorEnabled(false);
drawerToggle.setToolbarNavigationClickListener(navigationBackPressListener); //pop backstack
} else {
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.setToolbarNavigationClickListener(drawerToggle.getToolbarNavigationClickListener()); //open nav menu drawer
}
}
This is the other key part of the BaseActivity. Basically this method checks whether you are at the root fragment and sets up the indicator accordingly. Notice that it changes the listener depending on how many fragments are in the back stack.
Then there is the BaseFragment:
#Override
public void onResume() {
super.onResume();
getActivity().setTitle(getTitle());
}
protected abstract String getTitle();
The code above shows how the title is handled by the fragments.
Try something like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar()!=null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
final ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(drawerToggle);
drawerToggle.syncState();
final View.OnClickListener originalToolbarListener = drawerToggle.getToolbarNavigationClickListener();
final View.OnClickListener navigationBackPressListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
getFragmentManager().popBackStack();
}
};
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
drawerToggle.setDrawerIndicatorEnabled(false);
drawerToggle.setToolbarNavigationClickListener(navigationBackPressListener);
} else {
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.setToolbarNavigationClickListener(originalToolbarListener);
}
}
});
// Though below steps are not related but I have included to show drawer close on Navigation Item click.
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
/**
* handle item clicks using id
*/
drawer.closeDrawer(GravityCompat.START);
return true;
}
});
}
Handle the drawer state onBackPressed:
#Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
To reload previous fragment on back press, always add the fragment transaction to back stack like this:
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
SomeFragment fragmentToBeLoaded = new SomeFragment();
fragmentTransaction.replace(R.id.fragment_container, fragmentToBeLoaded,
fragmentToBeLoaded.getName());
fragmentTransaction.addToBackStack(fragmentToBeLoaded.getName());
fragmentTransaction.commit();
To dynamically change the page title, you can call this from every Fragments onStart or onResume method:
#Override
public void onStart() {
super.onStart();
getActivity().setTitle("Title for fragment");
}
Note: I have considered standard layout declaration and thus I have not included any layouts.
"Page title - Changing the page titles whenever a fragment in pushed and popped"
When you remove a fragment, there is the method isRemoving(). It helps to change title back.
#Override
public void onStop() {
super.onStop();
if (isRemoving()) {
// Change your title here
}
}
"functionality to Menu and Back nav"
Suggestion: we have to rely on the default android navigation system. If we use addToBackStack() for our fragments, in theory we don't have to override onBackPressed() at all.
"App does not redefine the expected function of a system icon (such as the Back button)."
"App supports standard system Back button navigation and does not make use of any custom, on-screen "Back button" prompts."
Core App Quality: https://developer.android.com/distribute/essentials/quality/core.html
"Managing the Hamburger/Back button at left top"
I suggest to use activity instead of 'MainActivityDetailFragment' to avoid complication.
Add this in your MainActivity where you are calling Fragments. getBackStackEntryCount() Return number of fragments in the back stack. where the fragment on the bottom of the stack has index 0. popBackStack() Pop the top Fragment off the back stack
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
getSupportFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
return true;
}
And in your Fragment where you want to go back use this function
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
getActivity().onBackPressed();
}
return true;
}
Ok, after a lot of tests I finally succeeded to setup a good navigation. I needed exactly the same as you, the only difference is that I am using v4 Fragments, but I don't think this will change anything here.
I am not using ActionBarDrawerToggle since the latest examples from Google do not use this component anymore.
The solution below also works for deep navigation: parent activity --> fragment --> fragment etc.
The only change needed in the Fragments is to change the title:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getActivity().setTitle(R.string.targets);
}
In the parent Activity onCreate method, I initialize the following:
mNavigationView = (NavigationView) findViewById(R.id.navigation_view);
setupDrawerContent(mNavigationView);
final Toolbar toolbar = (Toolbar) findViewById(R.id.drawer_toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu_24);// Set the hamburger icon
getSupportActionBar().setDisplayHomeAsUpEnabled(true);// Set home button pressable
// Handle the changes on the actionbar
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// When no more fragments to remove, we display back the hamburger icon and the original activity title
if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu_24);
setTitle(R.string.app_name);
}
// Else displays the back arrow
else {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_arrow_back_24);
}
}
});
Here is now the code to handle the action on the Home button:
#Override
public boolean onOptionsItemSelected(MenuItem item){
// Close the soft keyboard right away
Tools.setSoftKeyboardVisible(mViewPager, false);
switch (item.getItemId()) {
case android.R.id.home:
// When no more fragments to remove, open the navigation drawer
if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
mDrawerLayout.openDrawer(GravityCompat.START);
}
// Removes the latest fragment
else {
getSupportFragmentManager().popBackStack();
}
return true;
}
return super.onOptionsItemSelected(item);
}
And finally the code to handle the back press action:
#Override
public void onBackPressed() {
// When no more fragments to remove, closes the activity
if (getSupportFragmentManager().getBackStackEntryCount() <= 0) {
super.onBackPressed();
}
// Else removes the latest fragment
else {
getSupportFragmentManager().popBackStack();
}
}
NOTE: I am using an AppCompatActivity, a NavigationView and the theme Theme.AppCompat.Light.NoActionBar.
I'm trying to develop an application using the Navigation drawer template of Android Studio. So, I created a new project using this template. But when I run the program and click on a menu item, the view doesn't change. I searched everywhere on internet, but I didn't see how I can handle this.
This is the code provided by Android Studio:
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_camara) {
} else if (id == R.id.nav_gallery) {
} else if (id == R.id.nav_slideshow) {
} else if (id == R.id.nav_manage) {
} else if (id == R.id.nav_share) {
} else if (id == R.id.nav_send) {
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
What I want to achieve is to replace the current view with the appropriate view for the menu item clicked.
Is Fragment the best way to do this or should I create a different activity for each menu item?
In your case adding fragment will be the best solution.
create a fragment BlankFragment.java
public class BlankFragment extends Fragment {
public BlankFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
and create fragment_black.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.above_inc.shyam.drawer.BlankFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/hello_blank_fragment" />
</FrameLayout>
now replace your method
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
Fragment fragment = null;
if (id == R.id.nav_camera) {
// Handle the camera action
fragment = new BlankFragment();
} else if (id == R.id.nav_gallery) {
} else if (id == R.id.nav_slideshow) {
} else if (id == R.id.nav_manage) {
} else if (id == R.id.nav_share) {
} else if (id == R.id.nav_send) {
}
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment).commit();
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
add below code in your content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.above_inc.shyam.drawer.MainActivity"
tools:showIn="#layout/app_bar_main">
<FrameLayout
android:id="#+id/frame_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>
you can also add more fragment to other options same as camera in above code
Fragment will be the best way if you want your navigation drawer to be shown on the next screen when you click on the menu item.
If you will use activity then navigation drawer will not be shown unless you make a BaseActivity which extends Navigation Drawer and use everywhere. In this case you have to change acitivty transition animation too as new activity will pop up after clicking on the menu which may look odd.
Right click on your project -> New -> Fragment.
Edit the switch case accordingly.
#Passiondroid's answer on using Fragments only if you want Navigation drawer across all activities is spot on.
About starting an activity on click of the menu item, in one of my apps, I follow this,
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
switch(id)
{
case R.id.nav_profile:
selection = 1;
intent = new Intent("android.intent.action.WEB");
intent.putExtra("selection",selection);
startActivity(intent);
break;
case R.id.nav_books:
intent = new Intent("android.intent.action.WEBD");
selection = 2;
intent.putExtra("selection",selection);
startActivity(intent);
break;
case R.id.fav_quotes:
intent = new Intent ("android.intent.action.SHOWFAVTES");
selection = 3;
intent.putExtra("selection",selection);
startActivity(intent);
break;
default:
break;
}
To change and configure the Menu Item, you can find the "activity_main_drawer.xml" under res->Menu.
And on click of each of the menu items, you can start an activity as per your wish. You have declare the activities in your AndroidManifest.xml file before you use it.
Beginning from android studio > 3.5 you will realize when you create navigation drawer activity it generates fragments opens when drawer items are clicked.
you can a listener if you want to open an activity.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow,
R.id.nav_tools, R.id.nav_share, R.id.nav_send,R.id.nav_accounts)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
#Override
public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) {
int menuId = destination.getId();
switch (menuId){
case R.id.nav_gallery:
Toast.makeText(MainActivity.this,"You tapped gallery",Toast.LENGTH_LONG).show();
fab.hide();
break;
default:
fab.show();
break;
}
}
});
for full tutorial. Navigation Drawer
I know similar questions have been asked before but is there any clear easy to understand example to do this. What I usually do is create a project from Android Studio and select the one with navigation on it. But after I call my next activity it does not have the navigation drawer anymore. All I can do is just add a back button to the main activity in the toolbar. All I want is to have the ability to call the navigation drawer from all my activitie even those with a back button on the toolbar.
Is there a best practice to make the navigation drawer menu callable which is applicable to different scenarios. For example
main activity with drawer -> 2nd activity with toolbar but has back button. How do you call the drawer menu?
main activity with drawer -> 3rd activity with no toolbar. call navigation drawer thru swipe or buttons?
Basically Each Activity has its own actionBar and each actionbar can use own Navigation Drawer. So If You want to same navigation Drawer. You can use Fragment. You can make MainActivity has Navigation Drawer and Use FrameLayout in Mainactivity Then Replace Fragment. if You want each Activity has single Navigation Drawer. it is not Possible.
simply you can achieve that by having a base activity that you can extend from any activity that you want it to have the DL. for example
public abstract class SampleBaseActivity extends AppCompatActivity {
protected abstract boolean isHome();
private DrawerLayout drawerLayout;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawer_layout);
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
android.support.v7.widget.Toolbar toolbar = (android.support.v7.widget.Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
if (!isHome()) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_back);
} else {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu);
}
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
#Override public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
if (isHome()) {
drawerLayout.openDrawer(GravityCompat.START);
} else {
onBackPressed();
}
return true;
}
return super.onOptionsItemSelected(item);
}
}
now in every activity you like to have the drawer extend this activity instead of the normal AppCompatActivity
The setup
I have an activity whose contentView is an instance of a DrawerLayout, which has a navigation drawer with a drawer indicator displayed in the action bar. The activity contains a Fragment, let's call it ListFragment, which contains a list of options. When an option is clicked, I replace the ListFragment with a DetailFragment.
At this point, I would like to display an "up" navigation option instead of the navigation drawer indicator. I'm able to display the "up" icon if I disable the drawer indicator by calling mDrawerToggle.setDrawerIndicatorEnabled(false), but this only removes the drawer icon--it does not remove the functionality--that is, when I click the caret, the navigation drawer is still opened.
Additionally, in these subviews, I would like to disable the opening of the drawer by dragging from the edge of the screen. I have tried doing this by calling setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) but it doesn't seem to have disabled this functionality.
I have tried extending the ActionBarDrawerToggle class to prevent opening the drawer when the indicator is clicked--however, all that happens is that the overriding action (the "up" navigation) is performed, but the drawer still opens.
I have also implemented the steps in Switching between Android Navigation Drawer image and Up caret when using fragments . It works insofar as displaying the caret goes, but despite overriding the up button functionality, the menu still opens (the app does navigate back--it just also opens the drawer).
Question
So, long story short: is there any (preferably clean and elegant, but at this point I'll go with hacky) way to achieve these things when my layout root is a DrawerLayout:
Replace the drawer indicator with an "up" caret (tentatively doable via mDrawerToggle.setDrawerIndicatorEnabled(false))
Prevent the drawer from opening when the caret is clicked, and instead override with my own "up" functionality
Prevent the drawer from opening when I drag from the edge of the screen.
Edit
All right, it looks like if I both override ActionBarDrawerToggle AND onOptionsItemSelected, the menu does not open when I click the caret. But it still opens if I drag from the edge. Help!
Short Code
public void setDrawerState(boolean isEnabled) {
if ( isEnabled ) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
drawerToggle.onDrawerStateChanged(DrawerLayout.LOCK_MODE_UNLOCKED);
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.syncState();
}
else {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
drawerToggle.onDrawerStateChanged(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
drawerToggle.setDrawerIndicatorEnabled(false);
drawerToggle.syncState();
}
}
This is only part of the solution that I arrived at, but it was quite hard to figure out this bug, so I'm leaving this here for posterity's sake.
This how I was defining the ListView for my navigation drawer:
<ListView
android:id="#+id/listview_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start|bottom"
android:background="#111"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp" />
Even after calling setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) I was still able to slide the drawer open.
However, after changing the layout_gravity to "start" this problem seems to be resolved.
I was able to reproduce this issue in a sample, navigation-drawer-only app, so it does appear to be a reproducible issue not unique to my situation.
Building on sonida's answer. After calling setDrawerIndicatorEnabled(false), onNavigateUp wasn't being called still. So, I just created a new onClickListener that called it:
public void setDrawerState(boolean isEnabled) {
if ( isEnabled ) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.syncState();
}
else {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
drawerToggle.setDrawerIndicatorEnabled(false);
drawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onSupportNavigateUp();
}
});
drawerToggle.syncState();
}
}
also I think
drawerToggle.onDrawerStateChanged(DrawerLayout.LOCK_MODE_UNLOCKED);
has been depreciated, but it works fine without it.
You need to disable swipe and disable the actionbar home button:
Use the below code that builds on the code already given to disable swipe
public void setDrawerState(boolean isEnabled) {
if ( isEnabled ) {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
mDrawerToggle.onDrawerStateChanged(DrawerLayout.LOCK_MODE_UNLOCKED);
mDrawerToggle.setDrawerIndicatorEnabled(true);
mDrawerToggle.syncState();
getActivity().getActionBar().setHomeButtonEnabled(true);
}
else {
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
mDrawerToggle.onDrawerStateChanged(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
mDrawerToggle.setDrawerIndicatorEnabled(false);
mDrawerToggle.syncState();
getActivity().getActionBar().setHomeButtonEnabled(false);
}
}
Building on answer by #sonida And after using the tweaks given by #luca992 and #jai.
I tried above suggested codes But the "up" or "Back" arrow in left side of action bar was just not showing up in my app. But luckily I was able to fix that.
I had to add this extra line of code in setNavigationDrawerState() [Ref: android.support.v7.app.ActionBarDrawerToggle.setHomeAsUpIndicator ]
toggle.setHomeAsUpIndicator(R.drawable.ic_keyboard_backspace_white_24dp);
I downloaded the drawable: ic_keyboard_backspace_white_24dp from Material.io
Here is the complete code:
MainActivity.java -> onCreate()
DrawerLayout drawer;
ActionBarDrawerToggle toggle;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Start: Code automatically generated by Android Studio
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();
// End: Code automatically generated by Android Studio
// I had to add this listener as the "back" arrow was totally unresponsive
// Thanks to #luca992
toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onBackPressed();
}
});
// Start: Code automatically generated by Android Studio
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// End: Code automatically generated by Android Studio
// More custom code for other stuff
// ...
}
MainActivity.java -> setNavigationDrawerState()
public void setNavigationDrawerState(boolean isEnabled) {
if ( isEnabled ) {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
toggle.setDrawerIndicatorEnabled(true);
toggle.syncState();
}
else {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
toggle.setDrawerIndicatorEnabled(false);
// the extra line of code goes here
toggle.setHomeAsUpIndicator(R.drawable.ic_keyboard_backspace_white_24dp);
toggle.syncState();
}
MainActivity.java -> onBackPressed()
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else if(getSupportFragmentManager().getBackStackEntryCount() > 0){
getSupportFragmentManager().popBackStack();
}else {
super.onBackPressed();
}
}
MainActivity.java -> startFragment() [dummy function for example]
public void startFragment(){
MyFrag myFrag = new MyFrag();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frag_container ,myFrag)
.addToBackStack(null)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
MyFrag.java --> onViewCreated()
#Override
public void onViewCreated (View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
// Say, using an implemented interface Make call to MainActivitiy's setNavigationDrawerState() passing false
// setNavigationDrawerState(false)
// ...
}
MyFrag.java --> onDestroyView()
#Override
public void onDestroyView(){
// Say, using an implemented interface Make call to MainActivitiy's setNavigationDrawerState() passing true
// setNavigationDrawerState(true)
super.onDestroyView();
}
Is there any way to make sure that the navigation drawer stays on top of the content in the fragment?
I created a small test application with dummy data. 10 fragments with a corresponding numbered button and textview. The issue is with the fact that the fragment elements seem to have higher priority than the navigation drawer.
As seen in the screenshot, once I attempt to open up the "0 fragment" it instead opts to register the click on the button behind the navigation drawer. Pressing any other content item works fine, but this is as long as there are no other interactable items beneath them. What can I do to have the navigation drawer properly stay on top of the content behind it?
Set android:clickable="true" tag on sliding pane layout.
The problem seem not because of click focus,
Visit https://developer.android.com/training/implementing-navigation/nav-drawer.html#DrawerLayout
The main content view (the FrameLayout above) must be the first child in the DrawerLayout because the XML order implies z-ordering and the drawer must be on top of the content.
In my fragment drawer, I set TouchListener to return True.
It worked for me
mFragmentContainerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
I solved it in a different way.
Here is my code for setting up the Drawer:
/**
* Setup Navigation Drawer
*/
private void setDrawer() {
NavigationDrawerFragment mNavigationDrawerFragment = (NavigationDrawerFragment) getFragmentManager().findFragmentById(R.id.fragment_drawer);
mNavigationDrawerFragment.setup(R.id.fragment_drawer, (DrawerLayout) findViewById(R.id.drawer), mToolbar);
}
the setup method is inside my NavigationDrawerFragment, here is my code for it:
/**
* Users of this fragment must call this method to set up the navigation drawer interactions.
*
* #param fragmentId The android:id of this fragment in its activity's layout.
* #param drawerLayout The DrawerLayout containing this fragment's UI.
* #param toolbar The Toolbar of the activity.
*/
public void setup(int fragmentId, DrawerLayout drawerLayout, Toolbar toolbar) {
View mFragmentContainerView = (View) getActivity().findViewById(fragmentId).getParent();
DrawerLayout mDrawerLayout = drawerLayout;
//noinspection deprecation
mDrawerLayout.setStatusBarBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
ActionBarDrawerToggle mActionBarDrawerToggle = new ActionBarDrawerToggle(getActivity(), mDrawerLayout, toolbar, "Drawer opened", "Drawer closed") {
#Override
public void onDrawerClosed(View drawerView) {
super.onDrawerClosed(drawerView);
if (!isAdded()) return;
// Solution:
// Disable click event on views below Navigation Drawer
mFragmentContainerView.setClickable(false);
getActivity().invalidateOptionsMenu();
}
#Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
if (!isAdded()) return;
// Solution:
// Enable click event on views below Navigation Drawer
mFragmentContainerView.setClickable(true);
getActivity().invalidateOptionsMenu();
}
};
// Defer code dependent on restoration of previous instance state.
mDrawerLayout.post(new Runnable() {
#Override
public void run() {
mActionBarDrawerToggle.syncState();
}
});
//noinspection deprecation
mDrawerLayout.setDrawerListener(mActionBarDrawerToggle);
}
That's it
You have to set clickable, focusable and focusableInTouchMode in the highest view of your drawer's layout.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true">
Well, I found one solution for this issue. When the drawer is being opened you can bring the nav bar to the front by calling the bringToFront()-method on the layout you use. This makes sure the navigation drawer stays on top of any underlying content until a new item has been selected.
I.e:
#Override
public void onDrawerOpened(View drawerView)
{
activity.getActionBar().setTitle("Select content");
activity.invalidateOptionsMenu();
drawerLayout.bringToFront();
}