I am trying to set up proper navigation in my application, which replaces Fragments in a main content area, so I have only one single Activity. I have one main Fragment and several subFragments, for example a Fragment for preferences. Everything works fine when using the back button, but I want to implement the up navigation including icon in addition to this. I am using the ActionBar fetched with Activity.getSupportActionBar() together with a Toolbar from appcompat and an ActionBarDrawerToggle.
I followed this tutorial when setting up my Drawer in the first place.
Current behavior:
When I start the app, the list/drawer icon is shown in the left part of the ActionBar. When I click this, the Drawer opens and I can select items. Sub Fragments are replaced into my content and the back button pops the stack, taking me back to the previous Fragment.
Missing behavior:
The list/drawer icon in the top left is never replaced by the back arrow icon and I cannot figure out how to implement this properly. The Drawer is always pulled out when clicking the list/drawer icon, no matter which Fragment I am in.
What have I tried:
I tried following this answer. It kinda works, meaning that the back arrow icon is set in the sub Fragments, but clicking the back arrow still opens the Drawer instead of providing up navigation. Also, when using the back button to go "up", the list/drawer icon is replaced by nothing.
I also tried following this answer. Here, the desired ActionBar behavior/look is implemented in the onCreate() method of the various Fragments. Using this I could get the back arrow up, but still the Drawer is pulled when clicking the arrow.
Various other minor things and hacks.
My questions:
What is wrong in my code below?
Is it correct/normal to use the combination ActionBar, Toolbar and ActionBarDrawerToggle to implement the Drawer navigation together with up navigation?
MyActivity.onCreate():
#Override
protected void onCreate(Bundle savedInstanceState)
{
// Other stuff
// Setup drawer.
mDrawerFragment = (DrawerFragment)
getSupportFragmentManager().findFragmentById(R.id.mm_navigation_drawer);
mDrawerFragment.initialize(this, (DrawerLayout)findViewById(R.id.mm_drawer_layout), toolbar);
}
DrawerFragment class
public class DrawerFragment extends Fragment
{
private MyActivity mMyActivity;
private MyActionBarDrawerToggle mMyBarDrawerToggle;
private DrawerLayout mDrawerLayout;
private FragmentDrawerListener mFragmentDrawerListener;
private View mContainerView;
public void initialize(MyActivity myActivity, final DrawerLayout drawerLayout, final Toolbar toolbar)
{
mMyActivity = myActivity;
mFragmentDrawerListener = mMyActivity;
mContainerView = myActivity.findViewById(R.id.mm_navigation_drawer);
mMyActionBarDrawerToggle = new MyActionBarDrawerToggle(myActivity, drawerLayout, toolbar, R.string.mm_drawer_open, R.string.mm_drawer_close);
mDrawerLayout = drawerLayout;
mDrawerLayout.setDrawerListener(mMyActionBarDrawerToggle);
mDrawerLayout.post(new Runnable()
{
#Override
public void run()
{
mMyActionBarDrawerToggle.syncState();
}
});
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState)
{
// Not relevant, just create and return the View.
}
}
MyActivity.onDrawerItemSelected()
The implementation of the interface FragmentDrawerListener is done in the MyActivity class. It simply replaces the content area with other Fragments, using FragmentTransactions.
#Override
public void onDrawerItemSelected(View view, int postion)
{
switch (postion)
{
case DrawerAdapter.ITEM_FILTERED_RECIPES:
showFilteredRecipesFragment();
break;
case DrawerAdapter.ITEM_SELECTED_RECIPES:
showSelectedRecipesFragment();
break;
case DrawerAdapter.ITEM_SHOPPING_LIST:
showShoppingListFragment();
break;
case DrawerAdapter.ITEM_SETTINGS:
showSettingsFragment();
break;
case DrawerAdapter.ITEM_ABOUT:
showAboutFragment();
break;
}
}
MyActionBarDrawerToggle class
public class MyActionBarDrawerToggle extends ActionBarDrawerToggle
{
private MyActivity mMyActivity;
private Toolbar mToolbar;
public MyActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, Toolbar toolbar, int openDrawerContentDescRes, int closeDrawerContentDescRes)
{
super(activity, drawerLayout, toolbar, openDrawerContentDescRes, closeDrawerContentDescRes);
mMyActivity = (MyActivity) activity;
mToolbar = toolbar;
}
#Override
public void onDrawerOpened(View drawerView)
{
super.onDrawerOpened(drawerView);
mMyActivity.invalidateOptionsMenu();
}
#Override
public void onDrawerClosed(View drawerView)
{
super.onDrawerClosed(drawerView);
mMyActivity.invalidateOptionsMenu();
}
#Override
public void onDrawerSlide(View drawerView, float slideOffset)
{
super.onDrawerSlide(drawerView, slideOffset);
mToolbar.setAlpha(1 - slideOffset / 2);
}
}
The DrawerFragment is inflated in the main layout using a simple, static Fragment instance like this:
<fragment
android:id="#+id/my_navigation_drawer"
android:name="com.my.company.gui.drawer.DrawerFragment"
android:layout_width="#dimen/my_nav_drawer_width"
android:layout_height="match_parent"
android:layout_gravity="start"
app:layout="#layout/my_drawer_navigation_fragment"
tools:layout="#layout/my_drawer_navigation_fragment">
</fragment>
If you're using material design for you app, then it is expected that you use the Toolbar to replace the Actionbar in your activity. You will still maintain your ActionBarDrawerToggle and functionality will remain the same.
As for your fragments, as long as they are 'housed' by the same activtiy (i.e the activity with the drawer), changing fragments will not cause the drawer toggle to change the back arrow. It will only change if you navigate to a new activity. Only then will the main activity be treated as home and with the other activities having a back arrow to navigate back
Related
Introduction
I have an activity (let's call BaseActivity) that initializes the drawer layout in onPostCreate
public void initDrawerLayout(){
setSupportActionBar(getYellowToolbar());
getSupportActionBar().setDisplayShowTitleEnabled(false);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerLayout.setScrimColor(getResources().getColor(R.color.blue_menu));
// Toggle
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
getYellowToolbar(), R.string.drawer_open, R.string.drawer_close) {
public void onDrawerClosed(View view) {
super.onDrawerClosed(view);
}
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
EventBus.getDefault().post(new UiHelper());
}
};
mDrawerLayout.addDrawerListener(mDrawerToggle);
findViewById(R.id.menu_back).setOnClickListener(new
View.OnClickListener() {
#Override
public void onClick(View view) {
mDrawerLayout.closeDrawer(Gravity.LEFT);
}
});
mExpandableListView = (ExpandableListView) findViewById(R.id.listmenu);
mExpandableListView.setOnGroupClickListener(this);
mExpandableMenuAdapter = getMenuAdapter();
mExpandableListView.setAdapter(mExpandableMenuAdapter);
mDrawerToggle.syncState();
}
Then I have a specific fragment working under an Activity which extends BaseActity.
This fragment adds to the top toolbar (yellowtoolbar) at onResume time an additional button inside R.menu.scanner_menu.
Toolbar toolbar = getYellowToolbar();
if (toolbar == null)
return;
toolbar.getMenu().clear();
toolbar.inflateMenu(R.menu.scanner_menu);
/**
* Add click listener on scan
*/
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
#Override
public boolean onMenuItemClick(MenuItem item) {
.
.
return false;
}
});
Situation
BaseActivity onPostCreate init drawer layout
ChildrenActivity extends BaseActivity
Fragment inflates new button onResume
Problem
When I reach this fragment from another fragment belonging to the same activity the inflate of the additional button is successful and I can click it with no problem.
While if I starts the application (so the onPostCreate of the BaseActivity is called) having the fragment to be shown at first so both the initDrawerLayout from BaseActivity and the inflate of the fragment are called almost at the same time the inflate of the additional button is unsuccessful and I cannot see it.
Additional info
I have tried to add a generic button in the fragment and clicking on it inflates one more time the menu button in the top toolbar and it correctly works. I think the problem seems to be the toolbar rendering by the drawer layout initialization that is still not completed the moment the fragment inflates the new button, so the toolbar just skip the new button and goes on with the BaseActivity.
I have MainActivity which is holding 2 fragments. First fragment has navigation drawer, the second fragment not. In the MainActivity i want to override onBackPressed() method so when the navigation drawer is opened, it has to close the fragment. But i don't know how to get fragment's navigation drawer's state from Activity. Here's the code :
MainActivity.java
#Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)){
drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
FragmentListProduct.java
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
mainView = inflater.inflate(R.layout.fragment_list_product_layout, container, false);
Toolbar toolbar = (Toolbar) mainView.findViewById(R.id.toolbar);
fragmentActivity.setSupportActionBar(toolbar);
drawerLayout = (DrawerLayout) mainView.findViewById(R.id.drawer_layout);
drawerToggle = new ActionBarDrawerToggle(fragmentActivity, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawerLayout.addDrawerListener(drawerToggle);
drawerToggle.syncState();
navigationView = (NavigationView) mainView.findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
navigationView.setCheckedItem(R.id.nav_cat_all);
}
Of course it gives me error Cannot resolve symbol darwerlayout.
Before i made drawerLayout as a static field, but i got warning Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run). So how to solve this problem, or maybe you have another solution :D
You can access the fragment using the tag, which you'll have to assign when invoking the fragment, like this:
getFragmentManager()
.beginTransaction()
.replace(R.id.content_frame, fragment, "the_tag_here")
.commit();
Access it using getFragmentManager().findFragmentByTag("the_tag")
#Override
public void onBackPressed() {
FragmentListProduct fragment = (FragmentListProduct) getFragmentManager().findFragmentByTag("the_tag_here");
if (fragment != null) {
DrawerLayout drawerLayout = fragment.drawerLayout;
//make drawerLayout public in the fragment
if (drawerLayout.isDrawerOpen(GravityCompat.START)){
drawerLayout.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
} else {
super.onBackPressed();
}
}
Solution 1:
Put your navigation drawer in your main activity.
open the second fragment in the new activity if you do not want navigation drawer there.
Solution 2:
Create a method in your fragment say checkNavigationDrawer().
Get a fragment reference in your activity like this
Fragment yourFragment = (YourFragment)getSupportFragmentManager().findFragmentById(R.id.containerId);
In your onBackPressed () call yourFragment.checkNavigationDrawer(); like this
#Override
public void onBackPressed() {
yourFragment.checkNavigationDrawer();
super.onBackPressed();
}
In your checkNavigationDrawer method check if the navigation drawer is open or not if it is open close it.
public void checkNavigationDrawer(){
if(drawerLayout.isOpen()){
drawerLayout.closeDrawers();
}
}
Hold your FragmentListProduct object in your MainActivity.
Create one method called isDrawerOpen() in the FragmentListProduct fragment.
Now you can call the isDrawerOpen() from MainActivity by using the FragmentListProduct class object.
Go for it!
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
I've put a webview on the Navigation Drawer; the webview shows formatted text about the current UI so that the users can familiarize themselves with features.
Unfortunately, after scrolling up or down on the webview (horizontal scrolling of the webview is disabled), the navigation drawer closes itself after I lift my finger off the webview !
How can I prevent the Navigation Drawer from closing itself when the user lets go ?
EDIT:
additional findings ... I've attached an OnTouchListener() to the drawer's layout that does nothing except return true. This prevents the auto-closing from happening when touching an empty space on the Navigation Drawer. To see how this is done, check out http://android-er.blogspot.com/2014/01/android-drawerlayout-and-drawerlistener.html and look at the "experimental" comment.
Unfortunately it doesn't stop the Navigation Drawer from closing when the user doesn't scroll up/down perfectly on the webview.
For example, scrolling up and down on the webview will close the drawer if there is a very small horizontal component to the drag.
So the (hassle-free) solution to prevent the over-zealous closing of the drawer: once the drawer is open, lock it open and have a button trigger the closeDrawer(). The code below shows how to manage the lock/unlock status. It doesn't include the button to call closeDrawer() because that's quite basic.
in XML, the id of the DrawerLayout is :
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
in your Activity, the java code is :
import android.support.v4.widget.DrawerLayout;
public class MyActivity extends Activity implements DrawerLayout.DrawerListener
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.layoutwithdrawer);
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mDrawerLayout.setDrawerListener(this);
}
#Override
public void onDrawerClosed(View arg0) {
// allow swiping to open the drawer
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
}
#Override
public void onDrawerOpened(View arg0) {
// disable swiping so that the drawer can't be closed by accident when scrolling through webview
mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN);
}
#Override
public void onDrawerSlide(View arg0, float arg1) {}
#Override
public void onDrawerStateChanged(int arg0) {}
}
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();
}