如何在 android-导航库中隐藏 BottomNavigationView

how to hide BottomNavigationView on android-navigation lib

我正在尝试在我的 APP 中使用 android-navigation 库,并且按照教程中的说明进行操作。我只想在我的 APP 中使用一个单曲 activity。我对一个问题感到困惑。一些不想要 BottomNavigationView 的片段,我该如何隐藏它。 这是我的 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>

这是我的 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
}}

然后 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" />

我知道我可以在导航到 settingFragment 时将 BottomNavigationView 设置为消失。然后在回到我的片段时将 BottomNavigationView 设置为可见。但这很奇怪。任何可以帮助我的人,在此先感谢。

您可以在 activity 的 onCreate 中执行类似的操作。当导航栏中的项目被选中时,它会根据片段 ID 显示或隐藏导航。

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
   
}

@Samuel Grogan 的回答是正确的,但是如果你遇到设备旋转时底部导航 shows/hides 意外的问题,你可以使用 ViewModel 来解决这个问题:

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()
            }
        }
    }
}

接受的答案有效,它是官方文档中推荐的答案,但正如评论中所述,它确实会导致一些闪烁,因为回调是在附加片段之前执行的。

我发现下面的答案更灵活,并且可以更好地处理动画:

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)

您可以根据片段之间的过渡对其进行自定义,方法是选择不同的动画(在我的示例中是幻灯片),或者在另一个生命周期回调中进行调用。

官方 Android 网站上有一个解决方案:

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
   }
}

是的,这取决于。我按照 android.dev 上的指南说的那样做了。主 activity 布局的顶部是工具栏,中间是片段根容器,底部是底部导航视图。当我隐藏底部导航时,它每次都会在任何片段上调整大小。现在我明白我需要多个导航图,我需要不同的根容器来存放有或没有 navigation/toolbar

的片段

回复晚了,但我认为这很好用。

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()
    }
}

现在我们只需要使用lifecycleScope

lifecycleScope.launchWhenResumed {
                navController.addOnDestinationChangedListener { _, destination, _ ->
                    when (destination.id) {
                        R.id.home, R.id.booking, R.id.profile -> navigationView.show()
                        else -> navigationView.hide()
                    }
                }
            }

处理此问题的最佳方法是在没有刷新故障的情况下也没有 ViewModel 是在 when 条件之后调用 setupWithNavController 方法。

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)

我已经这样做了,而且效果很好。

PreferenceFragmentCompat

覆盖 PreferenceFragmentCompat 的 LifeCycle 方法。

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
}