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"/>
Related
Need the BottomNavigationView to always show by default (without any user interaction).
<?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=".presentation.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/AppPopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="#layout/content_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The content_main.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/app_bar_main">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment_content_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/drawer_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
Home fragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".presentation.ui.home.HomeFragment"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPagerContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"/>
<FrameLayout
android:id="#+id/ad_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_anchorGravity="bottom"
android:background="#color/colorPrimary"
app:itemIconTint="#color/tab_selector"
app:itemTextColor="#color/tab_selector"
app:menu="#menu/bottom_navigation_menu"/>
</androidx.appcompat.widget.LinearLayoutCompat>
Unfortunately it seems that there is no answer yet on this anywhere. I tried to set layout_behavior, scrollFlags and layout_anchorGravity to bottom on BottomNavigationView but nothing works.
If I remove the scrollFlags on Toolbar only then the BottomNavigationView shows, but need the Toolbar having a scroll behavior but leaving the BottomNavigationView untouch.
The problem that the BottomNavigationView is affected by the scrolling flag set with app:layout_behavior in the activity container.
There is a couple of approaches you'd use to fix this:
First: add the BNV just below the CoordinatorLayout to be a part of the activity; so the layout would be:
<ConstraintLayout>
<CoordinatorLayout>
<AppBarLayout>
<content_main>
<frameLayout>
<BottomNavigationView>
Drawback: you could have other fragments transacted in the FragmentContainerView and the BNV won't be hidden in them. You can hide the BNV by setting its visibility.
Second: Transfer the ToolBar to the fragment; and set it when the fragment is created. The layout scheme would be:
Activity: just have the FragmentContainerView
Home Fragment
<ConstraintLayout>
<CoordinatorLayout>
<AppBarLayout>
<content_main>
<frameLayout>
<BottomNavigationView>
The drawback to this that you've to create and change the ToolBar for any fragment transaction in the FragmentContainerView.
Also you'd have a look at this similar question.
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!
Hello there I want to hide bottom nav when recylerview scrolls up but not when the recylerview is scrolled down and when the recylerview stop I want the bottom nav to appear
also like a slide animation like how a bottom sheet has
I have one main activity in which I have given bottom nav and a fragment container as an Include the fragment container contains all of my fragments
Here is my code
Note if anyone wants more reference of the code please tell me i will
update the question
ok i have used app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior" in bootom nav xml but its not working
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black"
tools:context=".MainActivity">
<include
android:id="#+id/fragment_container"
layout="#layout/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="#+id/bottom_navigation_view"
layout="#layout/bottom_navigation_view" />
</RelativeLayout>
bottom_nav_view.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.bottomnavigation.BottomNavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/bottom_navigation_view_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="10dp"
android:background="#drawable/bottom_navigation_style"
android:elevation="5dp"
app:itemIconSize="30dp"
app:itemIconTint="#color/white"
app:itemRippleColor="#android:color/transparent"
app:labelVisibilityMode="unlabeled"
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
app:menu="#menu/bottom_navigation">
</com.google.android.material.bottomnavigation.BottomNavigationView>
You need to use CoordinatorLayout to achieve this "scroll-hide" feature:
<androidx.coordinatorlayout.widget.CoordinatorLayout //root Layout
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.core.widget.NestedScrollView //or RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/main_linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
</androidx.core.widget.NestedScrollView>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/main_bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#android:color/white"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:menu="#menu/main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Demo: https://youtu.be/3Gpeg6M82n4
I am working on a project which has 2 NavHost Fragments. The First/Parent NavHost Fragment is attached to the Main Activity XML file and shows destinations like Login Fragments and Register Fragment based on a Parent Navigation Graph.
Now when a user signs in, new Navhost Fragment or Child Navhost Fragment which is attached to another XML file and associated with another Navigation Graph or Child Nav Graph Kicks in.
The problem I'm facing is instead of showing the second view, that is; the view associated with the child NavHost Fragment xml file, the app shows child Nav Graph destinations within the Parent NavHost Fragment.
It completely ignores the XML of Child NavHost Fragment file.
Main Activity XML
<?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:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_green_light">
<fragment
android:id="#+id/navHostFragment"
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_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
Child Fragment Container XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/holo_blue_light"
android:layout_margin="24dp"
android:padding="#dimen/dimen_24dp">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:translationZ="#dimen/dimen_1dp"
app:elevation="#dimen/dimen_0dp">
<include layout="#layout/toolbar" />
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="#+id/nestedNavHost"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
app:defaultNavHost="true"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:navGraph="#navigation/restaurant_nav_graph" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="#+id/drawerNavView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="#layout/nav_view_header"
app:menu="#menu/drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
As in the code above, the Toolbar, Drawer Layout, Padding etc in the child navhost xml file is completely ignored
Any help would be appreciated to resolve the issue.
I want to hide my bottom bar on scroll but a FAB should stay on the screen.
If I put the FAB on top of BottomNavigationView using anchors but it appears behind it.
If I put layout_insetEdge="bottom" to the BottomNavigationView then it works but make my tests fail (https://issuetracker.google.com/issues/70162122) so I cannot use that at the moment.
Layout:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".screens.main.MainActivity">
<FrameLayout
android:id="#+id/mainContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/scrolling_view_behaviour" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/mainNewQuestionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/spacing_8dp"
app:layout_anchor="#+id/mainBottomNavigationView"
app:layout_anchorGravity="top|right|end"
app:srcCompat="#drawable/create_question"
tools:visibility="visible" />
<android.support.design.widget.BottomNavigationView
android:id="#+id/mainBottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:itemBackground="#color/my_gray"
app:itemIconTint="#drawable/selector_bottombar_item"
app:itemTextColor="#drawable/selector_bottombar_item"
app:layout_anchor="#id/mainContainer"
app:layout_anchorGravity="bottom"
app:layout_behavior="#string/hide_bottom_navigation_view_behaviour"
app:menu="#menu/bottom_navigation_main" />
</android.support.design.widget.CoordinatorLayout>
Just use:
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_gravity="bottom"
.../>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_gravity="top"
app:layout_anchor="#id/bottom_navigation"
app:layout_anchorGravity="top|end"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
You seem to be having a Z-Ordering issue here.
You could try the fab.bringToFront() followed by fab.invalidate() for api's below 21, and ViewCompat.setZ(fab, someFloat) for api's above 21.
I used these methods to bring a custom FloatingActionMenu, consisting of a series of FAB's I wrote for a project, to the front so they would overlap everything on the screen.