Reputation: 784
I am trying to use android-navigation lib in my APP and I do the things as the tutorial said. I just wanna use one single activity in my APP. I am confused about one question. some fragments that just don't want the BottomNavigationView, how can I hide it. here is my main_layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:fitsSystemWindows="true"
app:popupTheme="@style/AppTheme.PopupOverlay" >
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/carkeeper_navigation"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/menu_bottom_nav"
app:itemTextColor="@color/bottom_nav_title_color_selector"
app:itemIconSize="@dimen/x40"
app:menu="@menu/menu_main_bottom_nav"
app:labelVisibilityMode="labeled">
</com.google.android.material.bottomnavigation.BottomNavigationView>
here is my mainActivity
class MainActivity : BaseActivity() {
private lateinit var navController: NavController
private lateinit var bottomNavigationView: BottomNavigationView
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity)
navController = Navigation.findNavController(this, R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(navController.graph, null)
setSupportActionBar(findViewById(R.id.toolbar))
bottomNavigationView = findViewById(R.id.menu_bottom_nav)
bottomNavigationView.setupWithNavController(navController)
bottomNavigationView.itemIconTintList = null
}}
then the navigaton_graph
<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/carkeeper_navigation"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.saicfinance.carkeeper.func.main.MainFragment"
android:label="MainFragment"
tools:layout="@layout/home_fragment">
</fragment>
<fragment
android:id="@+id/mineFragment"
android:name="com.saicfinance.carkeeper.func.mine.MineFragment"
android:label="@string/mine_title"
tools:layout="@layout/mine_fragment" >
<action android:id="@+id/action_mine_fragment_to_setting_fragment"
app:destination="@id/settingFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right"/>
</fragment>
<fragment
android:id="@+id/settingFragment"
android:name="com.freddy.func.setting.SettingFragment"
android:label="setting_fragment"
tools:layout="@layout/setting_fragment" />
I know I can set BottomNavigationView gone when navigating to settingFragment. then set BottomNavigationView visible when back to mine fragment. But that is strange. anyone who can help me, thanks in advance.
Upvotes: 35
Views: 35027
Reputation: 2574
Expand on other's answer here, the flickering issue and first fragment resizes issue both seems to be solved for me by using Handler.post to push the execution of show/hide of the nav view to the end of the UI queue. Seem to work pretty well.
I didn't use beginDelayedTransition as I just want it to appear and disappear immediately.
navController.addOnDestinationChangedListener { _, destination, _ ->
Handler(Looper.getMainLooper()).post {
when (destination.id) {
R.id.abcFragment, R.id.defFragment -> {
bottomNavigationView.visibility = View.GONE
}
else -> {
bottomNavigationView.visibility = View.VISIBLE
}
}
}
}
Upvotes: 2
Reputation: 199
This works for me
navController.addOnDestinationChangedListener { _, destination, _ ->
TransitionManager.beginDelayedTransition(bottomNavigationView, Fade())
if (destination.id == R.id.addExpensesFragment) {
bottomNavigationView.visibility = View.GONE
} else {
bottomNavigationView.visibility = View.VISIBLE
}
}
Upvotes: 4
Reputation: 1
PreferenceFragmentCompat
https://stackoverflow.com/a/62552740/18606060
Override the LifeCycle methods of PreferenceFragmentCompat.
class MySettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
// Called when the Fragment is visible to the user.
override fun onStart() {
super.onStart()
val mainActivity = activity as MainActivity
mainActivity.setBottomNavigationVisibility(View.GONE)
}
// Called when the Fragment is no longer started.
override fun onStop() {
super.onStop()
val mainActivity = activity as MainActivity
mainActivity.setBottomNavigationVisibility(View.VISIBLE)
}
}
// Toggle the visibility of BottomNavigationView in MainActivity.kt.
fun setBottomNavigationVisibility(visibility: Int) {
binding.bottomNav.visibility = visibility
}
Upvotes: 0
Reputation: 121
the best way to handle this without the refreshing glitches and also without a ViewModel
is to call setupWithNavController
method after your when condition.
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.dashboardFragment, R.id.globalParamsFragment, R.id.managementFragment
-> {
binding.bottomNavigation.visible()
binding.fabAdd.visible()
}
else -> {
binding.bottomNavigation.gone()
binding.fabAdd.gone()
}
}
}
NavigationUI.setupWithNavController(binding.bottomNavigation, navController)
I've done it this way and it works perfectly.
Upvotes: 2
Reputation: 1049
You could do something like this in your activity's onCreate. When ever an item in the nav bar is selected it will show or hide the nav based on the fragment id's.
private fun setupNav() {
val navController = findNavController(R.id.nav_host_fragment)
findViewById<BottomNavigationView>(R.id.bottomNav)
.setupWithNavController(navController)
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.mainFragment -> showBottomNav()
R.id.mineFragment -> showBottomNav()
else -> hideBottomNav()
}
}
}
private fun showBottomNav() {
bottomNav.visibility = View.VISIBLE
}
private fun hideBottomNav() {
bottomNav.visibility = View.GONE
}
Upvotes: 86
Reputation: 1201
Late Reply, But I think this works fine.
import android.animation.ValueAnimator
import android.graphics.drawable.BitmapDrawable
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import androidx.core.animation.doOnEnd
import androidx.core.view.drawToBitmap
import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.snackbar.Snackbar
/**
* Potentially animate showing a [BottomNavigationView].
*
* Abruptly changing the visibility leads to a re-layout of main content, animating
* `translationY` leaves a gap where the view was that content does not fill.
*
* Instead, take a snapshot of the view, and animate this in, only changing the visibility (and
* thus layout) when the animation completes.
*/
fun BottomNavigationView.show() {
if (visibility == View.VISIBLE) return
val parent = parent as ViewGroup
// View needs to be laid out to create a snapshot & know position to animate. If view isn't
// laid out yet, need to do this manually.
if (!isLaidOut) {
measure(
View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.AT_MOST)
)
layout(parent.left, parent.height - measuredHeight, parent.right, parent.height)
}
val drawable = BitmapDrawable(context.resources, drawToBitmap())
drawable.setBounds(left, parent.height, right, parent.height + height)
parent.overlay.add(drawable)
ValueAnimator.ofInt(parent.height, top).apply {
startDelay = 100L
duration = 300L
interpolator = AnimationUtils.loadInterpolator(
context,
android.R.interpolator.linear_out_slow_in
)
addUpdateListener {
val newTop = it.animatedValue as Int
drawable.setBounds(left, newTop, right, newTop + height)
}
doOnEnd {
parent.overlay.remove(drawable)
visibility = View.VISIBLE
}
start()
}
}
/**
* Potentially animate hiding a [BottomNavigationView].
*
* Abruptly changing the visibility leads to a re-layout of main content, animating
* `translationY` leaves a gap where the view was that content does not fill.
*
* Instead, take a snapshot, instantly hide the view (so content lays out to fill), then animate
* out the snapshot.
*/
fun BottomNavigationView.hide() {
if (visibility == View.GONE) return
val drawable = BitmapDrawable(context.resources, drawToBitmap())
val parent = parent as ViewGroup
drawable.setBounds(left, top, right, bottom)
parent.overlay.add(drawable)
visibility = View.GONE
ValueAnimator.ofInt(top, parent.height).apply {
startDelay = 100L
duration = 200L
interpolator = AnimationUtils.loadInterpolator(
context,
android.R.interpolator.fast_out_linear_in
)
addUpdateListener {
val newTop = it.animatedValue as Int
drawable.setBounds(left, newTop, right, newTop + height)
}
doOnEnd {
parent.overlay.remove(drawable)
}
start()
}
}
Now We just need to use lifecycleScope
.
lifecycleScope.launchWhenResumed {
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.home, R.id.booking, R.id.profile -> navigationView.show()
else -> navigationView.hide()
}
}
}
Upvotes: 0
Reputation: 449
Yes, it depends. I did it like guide on android.dev says. Main activity layout has toolbar on top, fragment root container in the middle and bottom nav view in the bottom. And it resizes everytime on any fragment when I hide bottom nav. Now I understand that I need multiple nav graphs, I need different root containers for fragments with or without navigation/toolbar
Upvotes: 0
Reputation: 181
There’s a solution to this on the official Android site:
https://developer.android.com/guide/navigation/navigation-ui
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.id == R.id.full_screen_destination) {
toolbar.visibility = View.GONE
bottomNavigationView.visibility = View.GONE
} else {
toolbar.visibility = View.VISIBLE
bottomNavigationView.visibility = View.VISIBLE
}
}
Upvotes: 14
Reputation: 770
The accepted answer works, and it's the one recommended in the official documentation, but as stated on comments, it does cause some flickering, as the callback is executed before the fragment is attached.
I find the below answer more flexible, and handles animations better:
supportFragmentManager.registerFragmentLifecycleCallbacks(object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentViewCreated(fm: FragmentManager, f: Fragment, v: View, savedInstanceState: Bundle?) {
TransitionManager.beginDelayedTransition(binding.root, Slide(Gravity.BOTTOM).excludeTarget(R.id.nav_host_fragment, true))
when (f) {
is ModalFragment -> {
binding.bottomNavigation.visibility = View.GONE
}
else -> {
binding.bottomNavigation.visibility = View.VISIBLE
}
}
}
}, true)
You can customize it depending on the transitions between your fragments, by choosing different animation (on my example it's a Slide), or by making the call at another lifecycle callback.
Upvotes: 23
Reputation: 1776
@Samuel Grogan answer is correct, but if you have a problem that bottom navigation shows/hides unexpected when the device is rotated, you can use ViewModel to solve this issue:
MainViewModel
class MainViewModel : ViewModel() {
private val _bottomNavigationVisibility = MutableLiveData<Int>()
val bottomNavigationVisibility: LiveData<Int>
get() = _bottomNavigationVisibility
init {
showBottomNav()
}
fun showBottomNav() {
_bottomNavigationVisibility.postValue(View.VISIBLE)
}
fun hideBottomNav() {
_bottomNavigationVisibility.postValue(View.GONE)
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val bottomNav: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
bottomNav.setupWithNavController(navController)
mainViewModel.bottomNavigationVisibility.observe(this, Observer { navVisibility ->
bottomNav.visibility = navVisibility
})
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.comment_fragment -> mainViewModel.hideBottomNav()
else -> mainViewModel.showBottomNav()
}
}
}
}
Upvotes: 8