The complete reference of the BottomNavigationView used is:
com.google.android.material.bottomnavigation.BottomNavigationView
The scenario is as follows, imagine you have 1 activity with a navigation menu at the bottom, the menu has 5 items (A, B, C, D, E), and there are also 2 hidden fragments (F, G) , all fragments extend androidx.fragment.app.Fragment
bottom_nav_menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu android:id="#+id/menu_menu" xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/navigation_a" android:title="" />
<item android:id="#+id/navigation_b" android:title="" />
<item android:id="#+id/navigation_c" android:title="" />
<item android:id="#+id/navigation_d" android:title="" />
<item android:id="#+id/navigation_e" android:title="" />
</menu>
navigation_default.xml:
<?xml version="1.0" encoding="utf-8"?>
<navigation android:id="#+id/mobile_navigation" app:startDestination="#+id/navigation_a">
<fragment android:id="#+id/navigation_a" tools:layout="#layout/fragment_a" />
<fragment android:id="#+id/navigation_b" tools:layout="#layout/fragment_b" />
<fragment android:id="#+id/navigation_c" tools:layout="#layout/fragment_c" />
<fragment android:id="#+id/navigation_d" tools:layout="#layout/fragment_d" />
<fragment android:id="#+id/navigation_e" tools:layout="#layout/fragment_e" />
<fragment android:id="#+id/navigation_f" tools:layout="#layout/fragment_f" /><!-- hidden -->
<fragment android:id="#+id/navigation_g" tools:layout="#layout/fragment_g" /><!-- hidden -->
</navigation>
Activity.java:
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_a,
R.id.navigation_b,
R.id.navigation_c,
R.id.navigation_d,
R.id.navigation_e,
R.id.navigation_f,
R.id.navigation_g).build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_main);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(activityMainBinding.navView, navController);
I use navController.navigate(destination) to navigate to these hidden fragments, in general the method will handle any automatic transitions made by the program, to reproduce the problem let's say you are in a visible fragment, for example C, now if we use the navigate function to go from C to F navController.navigate(R.id.navigation_f), then again from F to A navController.navigate(R.id.navigation_a), if the user manually clicks on C it will redirect him to A (which means the content of A will be displayed rather than the content of C), and what I noticed that to avoid this weird behavior I need to close the automatic circle, which means the cycle should be: [C->F->C->A] rather than: [C->F->A], any better options?
I end up using actions instead of navigating directly to a specific fragment, an action must have a source and a destination, so rather than using R.id.navigation_f I used R.id.action_c_to_f, to keep things simple, I created a class to handle all possible transitions in the app:
Navigation.java:
public class Navigation {
/**/
public static void navigate(#IdRes int resId){
/**/
int[] destinations = new int[]{
/**/
R.id.navigation_a,
R.id.navigation_b,
R.id.navigation_c,
R.id.navigation_d,
R.id.navigation_e,
R.id.navigation_f,
R.id.navigation_g
};
/**/
int[] actions = new int[]{
/**/
R.id.action_c_to_f,
R.id.action_f_to_a,
/**/
};
/**/
int[][] transitions = new int[][]{
/**/
new int[]{ 2, 5, actions[0] }, // R.id.navigation_c -> R.id.navigation_f
new int[]{ 5, 0, actions[1] }, // R.id.navigation_f -> R.id.navigation_a
/**/
};
/**/
int i = indexOf(destinations, navController.getCurrentDestination().getId());
/**/
int j = indexOf(destinations, resId);
/**/
for (int k = 0; k < transitions.length; k++){
/**/
if (transitions[k][0] == i && transitions[k][1] == j){
/**/
int x = k;
/**/
new Handler(Looper.getMainLooper()).post(() -> navController.navigate(transitions[x][2]));
/**/
return;
/**/
}
}
}
/**/
public static int indexOf(int[] array, int value){
/**/
for (int i = 0; i < array.length; i++){
/**/
if (array[i] == value){
/**/
return i;
/**/
}
}
/**/
return -1;
/**/
}
}
navigation_default.xml:
<?xml version="1.0" encoding="utf-8"?>
<navigation android:id="#+id/mobile_navigation" app:startDestination="#+id/navigation_a">
<fragment android:id="#+id/navigation_a" tools:layout="#layout/fragment_a" />
<fragment android:id="#+id/navigation_b" tools:layout="#layout/fragment_b" />
<fragment android:id="#+id/navigation_d" tools:layout="#layout/fragment_d" />
<fragment android:id="#+id/navigation_e" tools:layout="#layout/fragment_e" />
<fragment android:id="#+id/navigation_g" tools:layout="#layout/fragment_g" />
<fragment android:id="#+id/navigation_c" tools:layout="#layout/fragment_c">
<action android:id="#+id/action_c_to_f"
app:destination="#id/navigation_f"
app:enterAnim="#anim/nav_default_enter_anim"
app:exitAnim="#anim/nav_default_exit_anim"
app:popEnterAnim="#anim/nav_default_pop_enter_anim"
app:popExitAnim="#anim/nav_default_pop_exit_anim"
app:popUpTo="#+id/navigation_c"
app:popUpToInclusive="true" />
</fragment>
<fragment android:id="#+id/navigation_f" tools:layout="#layout/fragment_f">
<action android:id="#+id/action_f_to_a"
app:destination="#id/navigation_a"
app:enterAnim="#anim/nav_default_enter_anim"
app:exitAnim="#anim/nav_default_exit_anim"
app:popEnterAnim="#anim/nav_default_pop_enter_anim"
app:popExitAnim="#anim/nav_default_pop_exit_anim"
app:popUpTo="#+id/navigation_a"
app:popUpToInclusive="true" />
</fragment>
</navigation>
Note: I believe this issue is related to an internal bug in Android, this is just a workaround until this issue is resolved, normally navigating directly to a specific fragment should work without interfering with manual navigation of the user, this should work as if the user manually jumped to the visible|hidden fragment.
Related
I have an app which is using the one-activity-multiple-fragments architecture using navigation component.
For each fragment, I have a slide in/out animation like so:
anim_in.xml:
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="100%p"
android:toXDelta="0%"
android:duration="#android:integer/config_shortAnimTime" />
anim_out.xml:
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0%p"
android:toXDelta="100%"
android:duration="#android:integer/config_shortAnimTime" />
nav_graph.xml:
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/nav_graph"
app:startDestination="#id/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="com.example.monettests.MainFragment"
android:label="#string/app_name"
tools:layout="#layout/fragment_main" >
<action
android:id="#+id/action_mainFragment_to_settingsFragment"
app:enterAnim="#anim/anim_in"
app:popEnterAnim="#anim/anim_in"
app:popExitAnim="#anim/anim_out"
app:destination="#id/settingsFragment" />
<action
android:id="#+id/action_mainFragment_to_feedbackFragment"
app:enterAnim="#anim/anim_in"
app:popEnterAnim="#anim/anim_in"
app:popExitAnim="#anim/anim_out"
app:destination="#id/feedbackFragment" />
</fragment>
<fragment
android:id="#+id/settingsFragment"
android:name="com.example.monettests.SettingsFragment"
android:label="Settings"
tools:layout="#layout/fragment_settings" />
<fragment
android:id="#+id/feedbackFragment"
android:name="com.example.monettests.FeedbackFragment"
android:label="Send Feedback"
tools:layout="#layout/fragment_feedback" />
</navigation>
Navigation code in MainFragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.menu_main, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.menuMain_settingsItem -> {
findNavController().navigate(R.id.action_mainFragment_to_settingsFragment)
true
}
R.id.menuMain_feedbackItem -> {
findNavController().navigate(R.id.action_mainFragment_to_feedbackFragment)
true
}
else -> {
false
}
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
The problem with this is that it just animates the fragment itself but not the action bar.
What I have tried:
Manually animating the action bar in each fragment using listeners (unsuccessful)
Searching documentation for a way to do this (tried for hours but unsuccessful - shocked that there isn't a way to do this in the SDK)
I understand that this is desired fragment behavior, but I want to give the end user the illusion of animating between different pages and for that it's best if the action bar itself is part of the transition.
Is there any way to have a fragment transition that animates the fragment and the action bar, similar to how it's done with activities?
I have a Fragment with bottom tabs named TabsFragment
class BottomTabsFragment : Fragment(R.layout.fragment_tabs) {
private val bottomNavigationView: BottomNavigationView by lazy {
requireView().findViewById(R.id.bottom_navigation_view)
}
private val tabsNavigation: NavController by lazy {
(childFragmentManager.findFragmentById(R.id.tabs_host_fragment) as NavHostFragment).navController
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
bottomNavigationView.setupWithNavController(tabsNavigation)
}
}
Here is a menu for bottom navigation view:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#id/dashboard_navigation"
android:title="Dashboard" />
<item
android:id="#id/search_vehicle_navigation"
android:title="Search" />
<item
android:id="#id/todos_navigation"
android:title="Todos" />
<item
android:id="#id/activities_navigation"
android:title="Activities" />
<item
android:id="#id/more_navigation"
android:title="More" />
</menu>
Graph for bottom menu:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/tabs_navigation"
app:startDestination="#id/search_vehicle_navigation">
<include app:graph="#navigation/dashboard_navigation" />
<include app:graph="#navigation/search_vehicle_navigation" />
<include app:graph="#navigation/todos_navigation" />
<include app:graph="#navigation/activities_navigation" />
<include app:graph="#navigation/more_navigation" />
</navigation>
Everything is done according Google tutorials and samples and it does not work. Only 1 default fragment that is setted as StartDestination is saved (Becouse it is in stack to be displayed when user press back button):
https://developer.android.com/guide/navigation/navigation-multi-module
https://github.com/android/architecture-components-samples/tree/main/NavigationAdvancedSample
I have created Navigation Drawer Activity using Android Studio project template and added few menu items programatically and set the item selected listener to navigate to destination fragment.
Here what I am trying to do is clicking on each menu item added above should open the same destination fragment say HomeFragment but with different arguments so that I can reuse the layout.
So far it is working great with only one problem that the menu items are not highlighting correctly and Home item is always checked. I think this is because I have added the self link to home fragment. Is there any way to fix this?
MainActivity.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
navController = Navigation.findNavController(this, R.id.nav_host_fragment);
// Adding menu items
Menu menu = navigationView.getMenu();
SubMenu labels = menu.addSubMenu("Labels");
labels.add(R.id.group_labels, 201, 201, "Label 1").setIcon(R.drawable.ic_menu_gallery);
labels.add(R.id.group_labels, 202, 202, "Label 2").setIcon(R.drawable.ic_menu_gallery);
navigationView.invalidate();
appBarConfig = new AppBarConfiguration.Builder(R.id.home)
.setDrawerLayout(drawer)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfig);
NavigationUI.setupWithNavController(navigationView, navController);
// Navigation item click listener
navigationView.setNavigationItemSelectedListener(item -> {
if (item.getGroupId() == R.id.group_labels) {
HomeFragmentDirections.ActionHomeSelf action = HomeFragmentDirections.actionHomeSelf();
action.setLabel(item.getTitle().toString());
navController.navigate(action);
}
navigationView.setCheckedItem(item.getItemId()); // Not working
NavigationUI.onNavDestinationSelected(item, navController);
drawer.closeDrawer(GravityCompat.START);
return true;
});
}
mobile_navigation.xml
<navigation 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/mobile_navigation"
app:startDestination="#+id/home">
<fragment
android:id="#+id/home"
android:label="#string/menu_home"
android:name=".fragments.HomeFragment"
tools:layout="#layout/fragment_home">
<argument
android:name="label"
app:argType="string"
android:defaultValue="default label" />
<action
android:id="#+id/action_home_self"
app:destination="#id/home"
app:launchSingleTop="false">
</action>
<action
android:id="#+id/action_home_to_blank"
app:destination="#id/blankFragment" />
</fragment>
<fragment
android:id="#+id/blankFragment"
android:name=".BlankFragment"
android:label="fragment_blank"
tools:layout="#layout/fragment_blank" />
</navigation>
activity_main_drawer.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="#+id/home"
android:icon="#drawable/ic_menu_camera"
android:orderInCategory="0"
android:title="#string/menu_home" />
<item
android:id="#+id/blankFragment"
android:icon="#drawable/ic_menu_camera"
android:orderInCategory="0"
android:title="#string/hello_blank_fragment" />
</group>
<item
android:orderInCategory="200"
android:title="#string/drawer_menu_group_labels">
<menu>
<group
android:id="#+id/group_labels"
android:checkableBehavior="single" />
</menu>
</item>
<item
android:orderInCategory="300"
android:title="#string/drawer_menu_group_pages">
<menu>
<group
android:id="#+id/group_pages"
android:checkableBehavior="single" />
</menu>
</item>
</menu>
I've been trying to figure out how the default Navigation Drawer Activity template came with Android Studio navigates between different fragments. I understand that this menu is an implementation using AndroidX navigation component and navigation graph, but I just cannot understand how each menu item is mapped to its corresponding fragment. I don't see any listener or onNavigationItemSelected() etc. Can someone explain how the mapping between menuItem and corresponding fragment was achieved?
MainActivity.java:
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
mAppBarConfiguration = new AppBarConfiguration.Builder(
navController.getGraph())
.setDrawerLayout(drawer)
.build();
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
}
#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 onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
menu.xml:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
>
<group android:checkableBehavior="single">
<item
android:id="#+id/nav_home"
android:icon="#drawable/ic_menu_camera"
android:title="#string/menu_home" />
<item
android:id="#+id/nav_gallery"
android:icon="#drawable/ic_menu_gallery"
android:title="#string/menu_gallery" />
<item
android:id="#+id/nav_slideshow"
android:icon="#drawable/ic_menu_slideshow"
android:title="#string/menu_slideshow" />
</group>
</menu>
nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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/mobile_navigation"
app:startDestination="#+id/nav_home">
<fragment
android:id="#+id/nav_home"
android:name="com.buzzz.myapplication.ui.home.HomeFragment"
android:label="#string/menu_home"
tools:layout="#layout/fragment_home">
<action
android:id="#+id/action_HomeFragment_to_HomeSecondFragment"
app:destination="#id/nav_home_second" />
</fragment>
<fragment
android:id="#+id/nav_home_second"
android:name="com.buzzz.myapplication.ui.home.HomeSecondFragment"
android:label="#string/home_second"
tools:layout="#layout/fragment_home_second">
<action
android:id="#+id/action_HomeSecondFragment_to_HomeFragment"
app:destination="#id/nav_home" />
<argument
android:name="myArg"
app:argType="string" />
</fragment>
<fragment
android:id="#+id/nav_gallery"
android:name="com.buzzz.myapplication.ui.gallery.GalleryFragment"
android:label="#string/menu_gallery"
tools:layout="#layout/fragment_gallery" />
<fragment
android:id="#+id/nav_slideshow"
android:name="com.buzzz.myapplication.ui.slideshow.SlideshowFragment"
android:label="#string/menu_slideshow"
tools:layout="#layout/fragment_slideshow" />
</navigation>
Thank you very much.
As per the Update UI components with NavigationUI documentation, the setupWithNavController() methods hook up UI elements (such as your NavigationView) with the NavController.
Looking at the setupWithNavController() Javadoc:
Sets up a NavigationView for use with a NavController. This will call onNavDestinationSelected when a menu item is selected. The selected item in the NavigationView will automatically be updated when the destination changes.
So internally, this is setting up the appropriate listeners - both on the NavigationView to handle menu selections and on the NavController to update the selected item when the current destination changes.
Looking at the onNavDestinationSelected() Javadoc, it becomes clear how the menu items and navigation graph destinations are matched:
Importantly, it assumes the menu item id matches a valid action id or destination id to be navigated to.
So clicking on a menu item with android:id="#+id/nav_home" will navigate to the destination with android:id="#+id/nav_home".
I'm new student on xamarin android. so I don't know how to create a event click on that.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="#+id/nav_home"
android:icon="#drawable/ic_home_black_48dp"
android:title="Home" />
<item
android:id="#+id/nav_genre"
android:icon="#drawable/ic_toc_black_48dp"
android:title="Genres" />
<item
android:id="#+id/nav_audio"
android:icon="#drawable/ic_settings_input_antenna_black_48dp"
android:title="Audio" />
<item
android:id="#+id/nav_download"
android:icon="#drawable/ic_get_app_black_48dp"
android:title="Download" />
</group>
<item android:title="Account">
<menu>
<group android:checkableBehavior="single">
<item
android:id="#+id/nav_about"
android:icon="#drawable/ic_lock_open_black_48dp"
android:title="About"/>
<item
android:id="#+id/nav_signout"
android:icon="#drawable/ic_perm_identity_black_48dp"
android:title="Sign out"/>
</group>
</menu>
</item>
</menu>
<!-- your content layout -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:titleTextColor="#android:color/background_light" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<frameLayout
android:id:="#+id/frameContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
</LinearLayout>
</RelativeLayout>
<android.support.design.widget.NavigationView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:id="#+id/nav_view"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/navmenu" />
</android.support.v4.widget.DrawerLayout>
I want to when click that item 1 -> fragmenthome(there only listview)
I want to when click that item 2 -> fragmentgenres(there only listview)
They all be showing up in frameContainer.
This is how to handle click events and load fragments accordingly.
HomeFragment homFragment;
GenresFragment genresFragment;
int currentFragmentId=Resource.Id.nav_home;
Above declarations are to be made global in the Activity.
var navigationView = FindViewById<NavigationView> (Resource.Id.nav_view);
navigationView.NavigationItemSelected+= NavigationView_NavigationItemSelected;
CreateFragments ();
LoadInditialFragment ();
Add the above snippets in OnCreate.
void CreateFragments()
{
homeFragment = new HomeFragment ();
genresFragment = new GenresFragment ();
}
void LoadInditialFragment()
{
var transaction = FragmentManager.BeginTransaction ();
transaction.Add (Resource.Id.frameContainer, genresFragment).Hide(genresFragment);
transaction.Add (Resource.Id.frameContainer, homeFragment);
transaction.Commit ();
}
void NavigationView_NavigationItemSelected (object sender, NavigationView.NavigationItemSelectedEventArgs e)
{
if (e.MenuItem.ItemId != currentFragmentId)
SwitchFragment (e.MenuItem.ItemId);
drawerLayout.CloseDrawers ();
}
void SwitchFragment(int FragmentId)
{
var transaction = FragmentManager.BeginTransaction ();
switch (currentFragmentId)
{
case Resource.Id.nav_home:
transaction.Hide (homeFragment).Commit ();
break;
case Resource.Id.nav_genre:
transaction.Hide (genresFragment).Commit ();
break;
}
transaction = FragmentManager.BeginTransaction ();
switch (FragmentId)
{
case Resource.Id.nav_home:
transaction.Show (homeFragment);
transaction.Commit ();
break;
case Resource.Id.nav_genre:
transaction.Show (genresFragment);
transaction.Commit ();
break;
}
currentFragmentId = FragmentId;
}
In the Create Fragment Method All Fragments are instantiated initially and attached to the Fragment. Then all fragments except the fragment to be shown are hidden. Then as the user clicks on an item in NavigationView the current fragment is hidden and the fragment corresponding to the menu item is shown. In this approach each fragment will not be created each time user switches menu. Thus pages will load faster.