Hide/Show Bottom Navigation and Collapsible Toolbar on scroll in fragment - android

I am trying to implement show/hide behavior of bottom navigation and collapsible toolbar from a specific fragment of bottom navigation.
Here is my activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toTopOf="#id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?android:attr/windowBackground"
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And here is the fragment_home.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/purple_200">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|snap">
<TextView
android:id="#+id/text_header"
android:layout_width="match_parent"
android:layout_height="150dp"
android:gravity="center"
android:text="Parallax Area"
android:textSize="20sp" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="4dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Currently when I scroll, only 'Parallax Area (pink)' is showing/hiding but not bottom navigation in sync. When I remove the '#+id/text_header' then my bottom navigation also shows/hides. I want to do it for only this fragment, not for the others. I came across with nested coordinator layout implementations, none of them worked.
How can I solve this? Thanks in advance.

I just found the solution to show/hide the BottomNavigation in an Activity when scrolling a RecyclerView in a Fragment.
The core idea is to set HideBottomViewOnScrollBehavior
programmatically when you're navigating Fragments
First of all, if you're using Navigation component from Jetpack, use addOnDestinationChangedListener in the Activity witch holds the nav host to get the current showing Fragment.
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.scrollToHideFragment -> {
toggleBottomNavBehavior(true)
}
else -> {
toggleBottomNavBehavior(false)
}
}
}
In the above code, toggleBottomNavBehavior will be called when the destination has changed.
Add toggleBottomNavBehavior method in the Activity
private fun toggleBottomNavBehavior(scrollToHide: Boolean) {
// Get the layout params of your BottomNavigation
val layoutParams = binding.bottomNav.layoutParams as CoordinatorLayout.LayoutParams
// Apply LayoutBehavior to the layout params
layoutParams.behavior =
if (scrollToHide) HideBottomViewOnScrollBehavior<CoordinatorLayout>() else null
// Add margin to Nav Host Fragment if the layout behavior is NOT added
// bottom_nav_height is 56dp when using Material BottomNavigation
binding.navHostMain.updateLayoutParams<ViewGroup.MarginLayoutParams> {
setMargins(
0, 0, 0, if (scrollToHide) 0 else resources.getDimensionPixelSize(R.dimen.bottom_nav_height)
)
}
}
Now you can scroll in a Fragment and hide the BottomNav in the Activity!

Related

Android Coordinator layout causing problems in status bar and navigation bar

I just created a new project template and am seeing the following issues:
This is the new project with zero changes:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:windowDrawsSystemBarBackgrounds="true"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="#layout/content_main" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="#dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="#android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
and the main content:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<fragment
android:id="#+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
This puts the content behind the navigation bar however:
If I remove the app_bar_scrolling_view_behaviour, it sits on top of the navigation bar, but also on top of the toolbar, and anyway I need that behaviour for the scrolling interations with the toolbar AFAIK.
If I add the scroll flags to the toolbar:
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_height="?attr/actionBarSize" />
Now when I scroll up to get rid of the toolbar, it still can be seen in the status bar:
What am I doing wrong?
I have encountered in the past this problem, and I solved it by using Insetter library. It basically automatically adds insets on top and bottom based on status & navigation bars size.
In your code, you would add the id root at your CoordinatorLayout and then at Activity's onCreate you could do something like this (if you use binding, otherwise you can use the applyInsetter on any ViewGroup:
binding.root.applyInsetter {
type(statusBars = true) {
padding(left = false, top = true, right = false, bottom = false)
}
type(navigationBars = true) {
padding(left = false, top = false, right = false, bottom = true)
}
}

How to stop RecyclerView in fragment from pushing down and hiding the BottomNavigationView?

My app uses a BottomNavigationBar to switch between three fragments. One of the fragments contain a RecyclerView within a ConstraintLayout. The RecyclerView pushes down the BottomNavigationBar making it invisible and unusable. Here is the xml code:
The main activity with the BottomNavigationView
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="10"
android:layout_above="#id/bottom_navigation"
android:id="#+id/fragment_container"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_gravity="bottom"
android:layout_alignParentBottom="true"
android:background="?android:attr/windowBackground"
android:theme="#style/BottomNavigationTheme"
app:labelVisibilityMode="unlabeled"
app:menu="#menu/nav_menu"
app:layout_scrollFlags="scroll|exitUntilCollapsed" />
</LinearLayout>
The fragment with the RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
And this is how I change the fragments within the fragment container
private fun setCurrentFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, fragment)
commit()
}
}
I am new in android, so my answer could be wrong.
Replace layout_height = "match_parent" in your fragment with the recyclerview to layout_height = "wrap_content" (Firstly try for recyclerview, then try for the constraintlayout, if not works try to change for both recyclerview and constraintlayout)

No drawer view found with gravity LEFT error on Clicking menu items in drawerlayout

No drawer view found with gravity LEFT occurs whenever I click on the menu items in the navigation view. I am using a custom App bar. I am using openDrawer and closeDrawer functions. Below I pasting the code please help I am stuck on this for a week.
property.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".views.enforcement.PropertyActivity">
<include
android:id="#+id/header"
layout="#layout/property_header" />
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:openDrawer="end">
<fragment
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:navGraph="#navigation/property_nav" />
<com.google.android.material.navigation.NavigationView
android:id="#+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="#color/white"
app:itemBackground="#color/white"
app:itemIconTint="#color/color_light_blue"
app:itemTextColor="#color/color_light_blue"
app:menu="#menu/drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
</LinearLayout>
property.class
private fun initView() {
navController = findNavController(R.id.fragmentContainerView)
mDrawerLayout = findViewById<DrawerLayout>(R.id.drawerLayout)
navigationView.setupWithNavController(navController)
binding.header.menuIV.setOnClickListener {
if (mDrawerLayout.isDrawerOpen(GravityCompat.END)) {
mDrawerLayout.closeDrawers()
} else {
mDrawerLayout.openDrawer(GravityCompat.END)
}
}
You need to be explicit with closeDrawers() because it is inherently Gravity.START but you are calling Gravity.END.
Instead call:
if (mDrawerLayout.isDrawerOpen(GravityCompat.END)) {
mDrawerLayout.closeDrawer(GravityCompat.END)
}

Achieving proper scrolling with RecyclerView, android jetpack navigation and a collapsing Toolbar

I try to implement proper scroll behaviour with the following setup in my android application.
For navigation I use the jetpack navigation in combination with a Toolbar layout and a bottom navigation. I also use the principle of 'one activity, many fragments'. Each item of the bottom navigation launches a corresponding Fragment based on my navigation graph. This requires me to use a NavHostFragment in my layout.
My Toolbar layout is part of the activity layout and gets populated based on the current fragment. Some fragments require a collapsing Toolbar, which also gets added when needed. But when having a collapsing toolbar I face the following problem:
In the particular case I have a collapsing Toolbar, the NavHostFragment is populated with a RecyclerView. In other cases it appears I can add app:layout_behavior="#string/appbar_scrolling_view_behavior" to the RecyclerView and get the expected result, because the RecyclerView is a direct child of the CoordinatorLayout. In my case the RecyclerView is a child of a Fragment, which is basically a FrameLayout (according to the Layout Inspector). This leads to the problem, that the layout_behaviour on the RecyclerView has no effect as the RecyclerView is not a direct child of the CoordinatorLayout.
I couldn't come up with a working solution for this problem. Does anybody have an idea?
layout/activity_overview.xml
<androidx.constraintlayout.widget.ConstraintLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".OverviewActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/toolbarCoordiantor"
android:layout_marginTop="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/overview_bottom_navigation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<fragment android:layout_width="match_parent" android:layout_height="wrap_content"
android:id="#+id/overview_fragmentholder"
android:name="androidx.navigation.fragment.NavHostFragment"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
android:layout_marginBottom="?attr/actionBarSize"
android:layout_marginTop="?attr/actionBarSize"
app:navGraph="#navigation/nav_graph"
app:layout_constraintVertical_bias="1.0"/>
<include layout="#layout/toolbar"
android:id="#+id/toolbar"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/overview_bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foregroundGravity="bottom"
app:menu="#menu/navigation"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:itemBackground="#color/white"
app:itemIconTint="#color/bottom_navigation_color_states"
app:itemTextColor="#color/bottom_navigation_color_states"/>
layout/toolbar.xml
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|snapMargins"
android:minHeight="?attr/actionBarSize">
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/expandedToolbarContentContainer"
android:layout_marginTop="?attr/actionBarSize"
app:layout_collapseMode="parallax"/>
<androidx.appcompat.widget.Toolbar android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
style="#style/AppTheme.DarkToolbar"
android:id="#+id/toolbarView">
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/round_outline"
style="#style/AppTheme.DarkToolbar.Container"
android:id="#+id/toolbarContentContainer"/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
layout/list.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/dish_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layoutAnimation="#anim/layout_animation_fall_down"
/>
Try to wrap up host_fragment with NestedScrollView with needed behaviour, like this:
<androidx.core.widget.NestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:fitsSystemWindows="true"
android:transitionGroup="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/my_nav_host" />
</androidx.core.widget.NestedScrollView>
Jurij Pituljas approach will prevent scrollToPosition method of RecyclerView to work. Addidionally navigating back to fragments with RecyclerView will reset its scroll position to the top.
A better approach is described here. Even there is no need for the surrounding FrameLayouts in the referenced article. Instead of wrapping the fragment within a NestedScrollView, just wrap any non scrolling fragment layout views into a NestedScrollView.
Activity layout with NavHostFragment: No need to wrap the fragment into a scrolling view. Just set the layout_behavior.
...
<fragment
android:id="#+id/navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:navGraph="#navigation/navigation_graph" />
...
Fragment layout with root RecyclerView: Nothing special to do at all as RecyclerView has scrolling capabilities.
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
Fragment layout with e.g. TextView: Wrap within root NestedScrollView to add scrolling capabilities.
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="non scrolling content wrapped" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

Android FloatingActionButton with fragment and bottom navigation bar

I'm creating an android app with the following structure:
The main activity has a bottom navigation bar switching between 3 different fragments
2 of the fragments are going to display lists of items, with a floating action button (FAB) to add new items
The 3rd fragment is going to show something different, that doesn't need a FAB.
Based on this, it seems like the FAB should "belong" to the list fragments, and not the main activity.
I am currently have the problem that the FAB hides itself behind the bottom navigation bar, as in this question. There is a nice solution there, but it depends on the the FAB being in the same layout as the bottom navigation bar.
Is there a way to get the FAB to not hide itself behind the bottom navigation, without moving the FAB up to the main activity (which breaks the modularity of the fragments)?
My activity layout is as follows:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<android.support.design.widget.BottomNavigationView
android:id="#+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_insetEdge="bottom"
app:menu="#menu/navigation" />
</android.support.constraint.ConstraintLayout>
where the "fragment container" is replaced by the selected fragment as below:
TodoListFragment fragment = new TodoListFragment();
//if we were started with an intent, pass on any special arguments
fragment.setArguments(getIntent().getExtras());
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
and finally the fragment has layout as below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView 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/list"
android:name="..."
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".TodoListFragment"
tools:listitem="#layout/fragment_todolist_item" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:src="#drawable/ic_add_white_24dp"
android:layout_margin="16dp" />
</android.support.design.widget.CoordinatorLayout>
Problem is with your constraints. You are setting FrameLayout's height to match parent. Try this -
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/navigation"/>

Categories

Resources