简体   繁体   中英

Navigation Drawer back button in fragments

I started creating of app which use one activity (Navigation Drawer) and many fragments. But I unable to use toolbar back button to navigate back from fragments. Hardware back button works perfectly. I know that I need to override onOptionsItemSelected , catch android.R.id.home , check if there are something in back stack and than pop it. After changing fragment, "burger" button changes to "back arrow", but when I click on it onOptionsItemSelected never fired, just opens the NavigationDrawer menu.

Here the code from activity:

public class NavDrawerActivity extends AppCompatActivity implements ... {

    NavigationView navigationView;
    BottomNavigationView bottomNavigationView;
    ActionBarDrawerToggle toggle;
    FragmentManager fragmentManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_nav_drawer);

        fragmentManager = getSupportFragmentManager();

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);

        toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);

        drawer.addDrawerListener(toggle);

        toggle.syncState();

        // Set back button
        fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if (fragmentManager.getBackStackEntryCount() > 0) {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                } else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    toggle.syncState();
                }
            }
        });

        // Load default fragment
        changeFragment(new HomeFragment(), false);

        navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.setNavigationItemSelectedListener(this);

        View headerLayout = navigationView.getHeaderView(0);

        bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation);
        bottomNavigationView.setOnNavigationItemSelectedListener(this);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                Toast.makeText(this, "Back pressed", Toast.LENGTH_SHORT)
                        .show();
                onBackPressed();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);

        if (drawer.isDrawerOpen(GravityCompat.START))
            drawer.closeDrawer(GravityCompat.START);

        if (drawer.isDrawerOpen(GravityCompat.START)) {
            drawer.closeDrawer(GravityCompat.START);
        } else if (fragmentManager.getBackStackEntryCount() > 0) {
            fragmentManager.popBackStack();
        } else {
            super.onBackPressed();
        }
    }

    private void changeFragment(Fragment fm, boolean addToBackStack)
    {
        FragmentTransaction ft = fragmentManager.beginTransaction();
        ft.replace(R.id.frame_layout_content, fm);
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        if (addToBackStack) ft.addToBackStack(null);
        ft.commit();
    }

}

And how I change (replace) fragments from HomeFragment :

IndexDetailFragment newFragment = new IndexDetailFragment();
Bundle args = new Bundle();

args.putString(IndexDetailFragment.ARG_INDEX_ID, id);

newFragment.setArguments(args);

FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
transaction.replace(R.id.frame_layout_content, newFragment);
transaction.addToBackStack(null);

transaction.commit();

setNavigationOnClick() on the toolbar after setSupportActionBar(toolbar) :

setSupportActionBar(toolbar);
toolbar.setNavigationOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                Toast.makeText(getActivity(), "Back clicked!",     
                Toast.LENGTH_SHORT).show();
            }
        });

I have made a small app for reference

FirstFragment

 public class FirstFragment extends Fragment {


public FirstFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_first, container, false);
}

}

SecondFragment

public class SecondFragment extends Fragment {


public SecondFragment() {
    // Required empty public constructor
}


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_second, container, false);
}

}

MainActivity

public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar;
private ActionBarDrawerToggle drawerToggle;
private DrawerLayout mDrawerLayout;
private String TAG = "MainActivity";
private FragmentManager mFragmentManager;
private NavigationView mNavigationView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mToolbar = (Toolbar) findViewById(R.id.toolbar);
    if (mToolbar != null) {
        setSupportActionBar(mToolbar);
    }
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer);
    drawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
        @Override
        public void onDrawerOpened(View drawerView) {
            super.onDrawerOpened(drawerView);
        }

        @Override
        public void onDrawerClosed(View drawerView) {
            super.onDrawerClosed(drawerView);
        }
    };
    mDrawerLayout.addDrawerListener(drawerToggle);
    mNavigationView = findViewById(R.id.navigation);
    mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.first:
                    changeFragment(new FirstFragment(), true);
                    return true;
                case R.id.second:
                    changeFragment(new SecondFragment(), true);
                    return true;
            }
            return false;
        }
    });
    mFragmentManager = getSupportFragmentManager();
    changeFragment(new FirstFragment(), true);
}

@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
    super.onPostCreate(savedInstanceState);
    if (drawerToggle != null) {
        drawerToggle.syncState();
    }
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == android.R.id.home) {
        Log.i(TAG, "onOptionsItemSelected: Home Button Clicked");
        if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
            mDrawerLayout.closeDrawer(Gravity.START);
        } else {
            mDrawerLayout.openDrawer(Gravity.START);
        }
    }
    return super.onOptionsItemSelected(item);
}

@Override
public void onBackPressed() {
    if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
        mDrawerLayout.closeDrawer(Gravity.START);
    }
    if (mDrawerLayout.isDrawerOpen(Gravity.START)) {
        mDrawerLayout.closeDrawer(Gravity.START);
    } else if (mFragmentManager.getBackStackEntryCount() > 0) {
        mFragmentManager.popBackStack();
    } else {
        super.onBackPressed();
    }
}

private void changeFragment(Fragment fragment, boolean needToAddBackstack) {
    FragmentTransaction mFragmentTransaction = mFragmentManager.beginTransaction();
    mFragmentTransaction.replace(R.id.FRAME_CONTENT, fragment);
    mFragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    if (needToAddBackstack)
        mFragmentTransaction.addToBackStack(null);
    mFragmentTransaction.commit();
}
}

activity_main

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
            

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/abc_action_bar_default_height_material"
        android:background="@color/colorPrimaryDark"
        />

    <FrameLayout
        android:id="@+id/FRAME_CONTENT"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="@dimen/abc_action_bar_default_height_material" />
</FrameLayout>

<android.support.design.widget.NavigationView
    android:id="@+id/navigation"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:layout_gravity="start"
    android:layout_marginTop="@dimen/abc_action_bar_default_height_material"
    app:menu="@menu/drawermenu" />
  </android.support.v4.widget.DrawerLayout>

I had the same issue.

I finally solved by adding this in the onCreate method.

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if(getSupportFragmentManager().getBackStackEntryCount() == 0){
                drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
                menuToggle.setDrawerIndicatorEnabled(true);
            }else{
                drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
                menuToggle.setDrawerIndicatorEnabled(false);

            }
        }
    });

Hope it helps

Add below code in onCreate() method that's work for me

    getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
                getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                toolbar.setNavigationOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this, " Back Pressed ", Toast.LENGTH_SHORT).show();
                    }
                });
            } else {
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                drawerToggle = new ActionBarDrawerToggle(MainActivity.this, drawerLayout, toolbar,
                        R.string.app_name, R.string.app_name);
                drawerLayout.addDrawerListener(drawerToggle);
                drawerToggle.syncState();
            }
        }
    });

Simplest solution for Kotlin Developers

Just add this in your root activity where fragments resist

if (supportFragmentManager.backStackEntryCount > 0) {
            supportActionBar!!.setDisplayHomeAsUpEnabled(true)
            toolbar.setNavigationOnClickListener {
                if (supportFragmentManager.backStackEntryCount > 0) {
                    super.onBackPressed()
                } else {
                    supportActionBar!!.setDisplayHomeAsUpEnabled(false)
                    drawerLayout.addDrawerListener(toggle)
                    toggle.syncState()
                    drawerLayout.openDrawer(GravityCompat.START)
                }
            }
        } else {
            supportActionBar!!.setDisplayHomeAsUpEnabled(false)
            drawerLayout.addDrawerListener(toggle)
            toggle.syncState()
        }

Here, whenever setDisplayHomeAsUpEnabled is set true , I am showing back button. and on cliking it, I am calling super.onBackPressed() which is similar to what your back button does!

The thing that solved me the issue was this:

I used the code that came automatically with the NavigationDrawerActivity template, and it was something like this:

    NavigationView navigationView = _binding.navView;
    // Passing each menu ID as a set of Ids because each menu should be considered as top level destinations:
    _AppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_x, R.id.nav_y, R.id.nav_z)
            .setOpenableLayout(_binding.drawerLayout)
            .build();
    _navController = Navigation.findNavController(this, R.id.nav_host_content_main);
    NavigationUI.setupActionBarWithNavController(this, _navController, _AppBarConfiguration);
    NavigationUI.setupWithNavController(navigationView, _navController);

And I struggled a lot with many tricks and hacks, and nothing was working for me.

At some point, I have noticed that the fragments that I want to enable the back button are " being considered as top level destinations ", exactly what the comment says in the code above. Then I removed those sub-fragments and BOOM that was it, with not a single hack or line of code needed, this is provided out of the box , I just needed to pay attention to it.

So in my case, I changed to the below code:

NavigationView navigationView = _binding.navView;
// Passing each menu ID as a set of Ids because each menu should be considered as top level destinations:
_AppBarConfiguration = new AppBarConfiguration.Builder(R.id.nav_x)
        .setOpenableLayout(_binding.drawerLayout)
        .build();
_navController = Navigation.findNavController(this, R.id.nav_host_content_main);
NavigationUI.setupActionBarWithNavController(this, _navController, _AppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, _navController);

: Just removed R.id.nav_y, R.id.nav_z in the AppBarConfiguration

I struggled with this for a while. I got it to work by:

  1. setting my the drawer layout in MainActivity.kt:
    val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
         val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

    val navController = navHostFragment.navController
    findViewById<NavigationView>(R.id.nav_view).setupWithNavController(navController)
        appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)
  1. Include the toolbar layout in each of the fragments that I need the Toolbar:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            />

</com.google.android.material.appbar.AppBarLayout>
</LinearLayout>
  1. Add the following code to the home fragment of the drawer, the one from which other fragments are called using the drawer. This gets the hamburguer icon to work, onside the onViewCreated method:
     var myToolbar = requireActivity() .findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbar) myToolbar.inflateMenu(R.menu.menu) val drawerLayout = requireActivity().findViewById<DrawerLayout>(R.id.drawer_layout) val toggle = ActionBarDrawerToggle( activity, drawerLayout, myToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) toggle.setDrawerIndicatorEnabled(true) drawerLayout.addDrawerListener(toggle) toggle.syncState()
  2. Add the following code in the kotlin code for the fragments that are after the home fragment, this will get the back arrow:
      val navController = findNavController()
        val appBarConfiguration = AppBarConfiguration(navController.graph)

        view.findViewById<Toolbar>(R.id.toolbar)
            .setupWithNavController(navController, appBarConfiguration)
  1. Edit the navigation graph to make sure the fragments are connected correctly

Hope this helps everyone struggling with this :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM