I am applying Single Activity Architecture, and I would like to use the top fixing bar using the toolbar.
Within my project, the form of the toolbar varies from fragment to fragment, and the form is as follows:
a few(1 or 2) fragment with drawer
In the MainFragment and one or two Fragments, the toolbar has a drawerLayout and two menu option, and in all Fragments except for that, it has a back button and no menu option.
But I don't know how to change toolbar's layout for each fragment.
I have two ideas for changing toolbar layout. The first way is to use the toolbar for Activity xml and change the menu on the code when I move the fragment using menuProvider. Like this:
MainActivity.kt
private fun initToolbar() {
setSupportActionBar(binding.content.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_action_open_drawer)
supportActionBar?.setDisplayShowTitleEnabled(false)
binding.content.toolbar.title = "test"
}
private fun initMenuProvider() {
addMenuProvider(object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.toolbar_activity, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
android.R.id.home -> { // for open drawerLayout
binding.drawerLayout.openDrawer(GravityCompat.START)
}
}
return true
}
})
}
fun changeToolbar(menuProvider: MenuProvider, title: String) {
supportActionBar?.setHomeAsUpIndicator(null)
addMenuProvider(menuProvider)
binding.content.toolbar.title = title
}
OtherFragment.kt
override fun onViewCreated() {
super.onViewCreated(view, savedInstanceState)
val menuProvider = object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.toolbar_nothing, menu) // empty menu layout
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return true
}
}
requireActivity().changeToolbar(menuProvider!!, "Other Fragment")
requireActivity().invalidateOptionsMenu()
}
This code succeeded in changing the dehaze mark to back button and fixing the toolbar's title to fragment title. However, there are still two menu options left, and the openDrawer() runs even when the go back button is pressed. And even if this way works properly, I will have to run the changeToolbar() of activity for every fragment's onViewCreated.
Another way is not to manage toolbars in activity, but to add toolbars to xml for each fragment. But I think this is going to be a very inefficient operation.
What method should I use?
I solved it in the following way.
Never use
setSupportActionBar()
In my project, the following conditions exist:
popBackStack()
).The solution was as follows:
Add each Toolbar Layout to MainActivity. (It doesn't matter if you want to create a toolbar_layout.xml
file and include the layout)
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/default_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="parent">
</androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.Toolbar
android:id="@+id/backstack_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone">
</androidx.appcompat.widget.Toolbar>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
android:layout_marginTop="?attr/actionBarSize"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
...
</androidx.constraintlayout.widget.ConstraintLayout>
Adjust the visibility of the toolbar required to move the fragment in Activity. In this project, Jetpack Navigation was used, so addOnDestinationChangedListener
from navController was used.
private fun initNavigationEvents() {
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.mainFragment,
R.id.fragmentA,
R.id.fragmentB,
)
)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
navController = navHostFragment.navController
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.mainFragment, R.id.fragmentA, R.id.fragmentB -> {
mViewDataBinding.defaultToolbar.visibility = View.VISIBLE
mViewDataBinding.backstackToolbar.visibility = View.GONE
}
else -> {
mViewDataBinding.defaultToolbar.visibility = View.GONE
mViewDataBinding.backstackToolbar.visibility = View.VISIBLE
}
}
}
...
}
Add DrawerLayout in Activity.
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="start">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
// toolbars
// FragmentContainerView
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
android:layout_marginEnd="-64dp"
android:fitsSystemWindows="true"
app:headerLayout="@layout/navigation_left_drawer_header"
app:menu="@menu/bottom_nav_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
If toolbar is set to ActionBar, you can inflate the menu through the MenuProvider
and specify the action of home through android.R.id.home
in onMenuItemSelected
.
However, this method should only be used if only one toolbar is used, and if you want to use the toolbar differently for each fragment, it should not be designated as an ActionBar. So, the menu must be inflated on the toolbar without specifying the ActionBar.
In Toolbar, the image of the Home button can be changed to toolbar.setNavigationIcon()
, and the click event can be set with the toolbar.setNavigationOnClickListener
.
private fun initDefaultToolbar() {
mViewDataBinding.defaultToolbar.inflateMenu(R.menu.toolbar_activity)
mViewDataBinding.defaultToolbar.setNavigationIcon(R.drawable.ic_action_open_drawer)
mViewDataBinding.defaultToolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.item1 -> {
Toast.makeText(this@MainActivity, "item1 clicked", Toast.LENGTH_SHORT).show()
return@setOnMenuItemClickListener true
}
R.id.item2 -> {
Toast.makeText(this@MainActivity, "item2 clicked", Toast.LENGTH_SHORT).show()
return@setOnMenuItemClickListener true
}
else -> {
return@setOnMenuItemClickListener false
}
}
}
mViewDataBinding.defaultToolbar.setNavigationOnClickListener {
mViewDataBinding.drawerLayout.openDrawer(GravityCompat.START)
}
}
private fun initBackstackToolbar() {
mViewDataBinding.backstackToolbar.setNavigationIcon(R.drawable.ic_action_back)
mViewDataBinding.backstackToolbar.setNavigationOnClickListener {
navController.popBackStack()
}
}
You can easily customize it by adding ImageView to indicate logo and TextView to indicate the title within the toolbar and adjusting visibility as needed.
<androidx.appcompat.widget.Toolbar
android:id="@+id/default_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/app_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_launcher_foreground"
android:visibility="@{!vm.titleVisibility}" />
<TextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{vm.title}"
android:visibility="@{vm.titleVisibility}" />
</androidx.appcompat.widget.Toolbar>
<androidx.appcompat.widget.Toolbar
android:id="@+id/backstack_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone">
<TextView
android:id="@+id/toolbar_title2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@{vm.title}"
android:visibility="@{vm.titleVisibility}" />
</androidx.appcompat.widget.Toolbar>
I implemented it by passing the title of the fragment from the fragment's onViewCreated to Activity and passing the data to the ViewModel and branching it.