└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Navigation-Component-TabLayout-with-Custom-Transition-Animation-and-Custom-Navigation-Listener 2 | This Tutorial will discuss how to implement custom transition animation when using Navigation Component provided by Android Jetpack and TabLayout. 3 | 4 | # Prerequisite: 5 | This tutorial assumes that you are familiar with the following: 6 | 1. Android Jetpack Navigation Component 7 | 2. TabLayout 8 | 9 | # Discussion 10 | If you are familiar with Android Jetpack Navigation Component, you will find that there is no direct implementation with the TabLayout. Actually, The Navigation Component intentionally doesn't support direct implementation with TabLayout as "Navigation focuses on elements that affect the back stack and tabs do not affect the back stack - you should continue to manage tabs with a ViewPager and TabLayout" Google Issue Tracker. 11 | 12 | - https://issuetracker.google.com/issues/122087752 13 | 14 | Here is How you could manage Tabs with ViewPager 15 | - https://developer.android.com/guide/navigation/navigation-swipe-view 16 | 17 | #### But What if you still want to implement any of the following case scenarios: 18 | 1. Navigation Component with TabLayout 19 | 2. Applying Custom Transition Animation 20 | 21 | # Case Scenario One 22 | For the first case scenario, you may have 5 tabs in the TabLayout to navigate between available destinations. We will supposed that you have created the TabLayout in the XML file as below 23 | 24 | 29 | 30 | 34 | 35 | 39 | 40 | 44 | 45 | 49 | 50 | 54 | 55 | 56 | 57 | So, Here is the Steps to achieve the desired behavior in the first case scenario; 58 | 1. Create a NavOption object. You have to make sure of creating only one instance of the same destination target (the destination fragment you are navigating to) by adding this parameter to the NavOption Object 59 | 60 | .setLaunchSingleTop(true) 61 | 62 | 2. You will also need to make sure that the navigation BackStack is cleared with every transition so that when you press the back button, you go for the start destination fragment, not the previous selected fragment. This can be achieved by adding the following parameters to the NavOption Object 63 | 64 | .setPopUpTo(navController.getGraph().getStartDestination(), false) 65 | 66 | 3. Pass the NavOption Object to the NavController.navigate() method to be applied for the navigation operation 67 | navController.navigate(item.getItemId(), null, navOptions); 68 | 69 | 4. Override the addOnTabSelectedListener() method to control what happen when you select a tab 70 | 5. Create s Switch/Case Statement to differentiate between different tabs based on their positions 71 | 6. Navigate to the desired destination fragment based on the tab position and don't forget to pass your NavOptions object. 72 | 7. Override the onBackPressed() method to check the tab related to the start destination fragment. 73 | 74 | Here is a complete code snippet 75 | 76 | @Override 77 | protected void onCreate(Bundle savedInstanceState) { 78 | .... 79 | NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); 80 | 81 | NavOptions navOptions = new NavOptions.Builder() 82 | .setLaunchSingleTop(true) 83 | .setPopUpTo(navController.getGraph().getStartDestination(), false) 84 | .build(); 85 | 86 | tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { 87 | @Override 88 | public void onTabSelected(TabLayout.Tab tab) { 89 | switch (tab.getPosition()) { 90 | case 0: 91 | navController.navigate(R.id.homeFragment, null, navOptions); 92 | break; 93 | case 1: 94 | navController.navigate(R.id.requestsFragment, null, navOptions); 95 | break; 96 | case 2: 97 | navController.navigate(R.id.chatFragment, null, navOptions); 98 | break; 99 | case 3: 100 | navController.navigate(R.id.notificationsFragment, null, navOptions); 101 | break; 102 | case 4: 103 | navController.navigate(R.id.profileFragment, null, navOptions); 104 | break; 105 | } 106 | } 107 | 108 | @Override 109 | public void onTabUnselected(TabLayout.Tab tab) { 110 | 111 | } 112 | 113 | @Override 114 | public void onTabReselected(TabLayout.Tab tab) { 115 | 116 | } 117 | }); 118 | } 119 | 120 | @Override 121 | public void onBackPressed() { 122 | tabLayout.getTabAt(0).select(); 123 | super.onBackPressed(); 124 | } 125 | 126 | # Case Scenario Two 127 | For this case scenario, you have the exact first scenario but you also want to apply a custom transition animation. 128 | 129 | Here is the Steps to achieve the desired behavior in the Second case scenario; 130 | 1. Create a NavOption object and pass the custom transition animation to it using the following methods 131 | 132 | .setEnterAnim(R.anim.slide_in_right) 133 | .setExitAnim(R.anim.slide_out_left) 134 | .setPopEnterAnim(R.anim.slide_in_right) 135 | .setPopExitAnim(R.anim.slide_out_left) 136 | 137 | 2. Create a NavOption object. You have to make sure of creating only one instance of the same destination target (the destination fragment you are navigating to) by adding this parameter to the NavOption Object 138 | 139 | .setLaunchSingleTop(true) 140 | 141 | 3. You will also need to make sure that the navigation BackStack is cleared with every transition so that when you press the back button, you go for the start destination fragment, not the previous selected fragment. This can be achieved by adding the following parameters to the NavOption Object 142 | 143 | .setPopUpTo(navController.getGraph().getStartDestination(), false) 144 | 145 | 4. Pass the NavOption Object to the NavController.navigate() method to be applied for the navigation operation 146 | navController.navigate(item.getItemId(), null, navOptions); 147 | 148 | 5. Override the addOnTabSelectedListener() method to control what happen when you select a tab 149 | 6. Create s Switch/Case Statement to differentiate between different tabs based on their positions 150 | 7. Navigate to the desired destination fragment based on the tab position and don't forget to pass your NavOptions object. 151 | 8. Override the onBackPressed() method to check the tab related to the start destination fragment. 152 | 153 | Here is a complete code snippet 154 | 155 | @Override 156 | protected void onCreate(Bundle savedInstanceState) { 157 | .... 158 | NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); 159 | 160 | NavOptions navOptions = new NavOptions.Builder() 161 | .setLaunchSingleTop(true) 162 | .setEnterAnim(R.anim.slide_in_right) 163 | .setExitAnim(R.anim.slide_out_left) 164 | .setPopEnterAnim(R.anim.slide_in_right) 165 | .setPopExitAnim(R.anim.slide_out_left) 166 | .setPopUpTo(navController.getGraph().getStartDestination(), false) 167 | .build(); 168 | 169 | tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { 170 | @Override 171 | public void onTabSelected(TabLayout.Tab tab) { 172 | switch (tab.getPosition()) { 173 | case 0: 174 | navController.navigate(R.id.homeFragment, null, navOptions); 175 | break; 176 | case 1: 177 | navController.navigate(R.id.requestsFragment, null, navOptions); 178 | break; 179 | case 2: 180 | navController.navigate(R.id.chatFragment, null, navOptions); 181 | break; 182 | case 3: 183 | navController.navigate(R.id.notificationsFragment, null, navOptions); 184 | break; 185 | case 4: 186 | navController.navigate(R.id.profileFragment, null, navOptions); 187 | break; 188 | } 189 | } 190 | 191 | @Override 192 | public void onTabUnselected(TabLayout.Tab tab) { 193 | 194 | } 195 | 196 | @Override 197 | public void onTabReselected(TabLayout.Tab tab) { 198 | 199 | } 200 | }); 201 | } 202 | 203 | @Override 204 | public void onBackPressed() { 205 | tabLayout.getTabAt(0).select(); 206 | super.onBackPressed(); 207 | } 208 | 209 | 210 | # Worthy to be mentioned 211 | If you apply this tutorial, You may achieve your desired behavior but you will violate two intended behavior 212 | 1. Cross fade animation between BottomNavigationView items is an intended behavior to follow the Material GuideLines. 213 | 2. Navigation focuses on elements that affect the back stack and tabs do not affect the back stack. 214 | 215 | - https://issuetracker.google.com/issues/138665563 216 | - https://issuetracker.google.com/issues/122087752 217 | 218 | # What's Next ? 219 | 220 | Navigation-Component-BottomNavigationView-with-Custom-Transition-Animation-and-Navigation-Listener 221 | 222 | https://github.com/KarimRedaHassan/Navigation-Component-BottomNavigationView-with-Custom-Transition-Animation-and-Navigation-Listener 223 | 224 | Navigation-Component-DrawerLayout-with-Custom-Transition-Animation-and-Custom-Navigation-Listener 225 | 226 | https://github.com/KarimRedaHassan/Navigation-Component-DrawerLayout-with-Custom-Transition-Animation-and-Custom-Navigation-Listener 227 | 228 | Navigation-Component-OptionsMenu-with-Custom-Transition-Animation-and-Custom-Navigation-Listener 229 | 230 | https://github.com/KarimRedaHassan/Navigation-Component-OptionsMenu-with-Custom-Transition-Animation-and-Custom-Navigation-Listener 231 | 232 | # Additional Resources 233 | 234 | https://developer.android.com/guide/navigation/navigation-getting-started 235 | 236 | https://developer.android.com/guide/navigation/navigation-ui 237 | 238 | https://codelabs.developers.google.com/codelabs/android-navigation/index.html#0 239 | 240 | https://developer.android.com/guide/navigation/navigation-swipe-view 241 | 242 | # Also See 243 | 244 | #### A Full List Of All My Tutorials 245 | 246 | https://github.com/KarimRedaHassan?tab=repositories 247 | 248 | --------------------------------------------------------------------------------