I've a single Activity with a BottomNavigationView inside of it's layout:
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior"
app:menu="#menu/menu_home_bottom_navigation"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
My bottom_avigation changes nav_host FragmentContainerView with fragments. All of this fragments have NestedScrollView or RecyclerView and because of app:layout_behavior="#string/hide_bottom_view_on_scroll_behavior", my bottom_navigation automatically hides/shows on scrollDown/scrollUp.
I saw this question: Hide/Show bottomNavigationView on Scroll
. I'm currently using the answer given by Abhishek Singh but the problem is not this.
This is my problem: Imagine FragA and FragB both have RecyclerViews but FragA has less items causing that all items fit to the screen and not scrollable. Now when I switch from FragA to FragB and then scrollDown, bottom_navigation hides with animation and if I press back button I cannot see bottom_navigation anymore and because FragA is not scrollable I cannot make it visible by scrolling.
I've also tried bottom_navigation.visibility = View.Visible in FragA onResume event, but still does not work. I think that it somehow translates bottom_navigation to the bottom and because of that this code does not help.
So how can I fix this issue?
Since there is no part from your code here my solution would be to listen on the back button:
maybe you can check this article it would be helpful
Android: onBackPressed() for Fragments
And there change the visibility for the BottomNavigationView.
I found the answer. instead of changing the visibility property of the bottom_navigation, I wrote two extension functions on BottomNavigationView for hiding/showing it:
private fun BottomNavigationView.showUp() {
animate().setDuration(200L).translationY(0f).withStartAction { visibility = View.VISIBLE }.start()
}
private fun BottomNavigationView.hideDown() {
animate().setDuration(200L).translationY(height.toFloat()).withEndAction { visibility = View.GONE }.start()
}
Now in onResume of FragA I have this:
override onResume() {
super.onResume()
bottom_navigation.showUp()
}
Related
I am currently working on a school project that displays your timetable, grades, subjects and so on. To use any functionality in the Android application you have to be logged in. Here comes my problem:
When the users starts the application for the first time, they should see a login fragment. Once the login is completed they will be presented with a setup screen where they can choose color themes for grades and other user specific things. Only then should they be presented with the actual main fragment. On the second start the user should directly see the main fragment
The main fragment is a FragmentContainerView and a BottomNavigationBar that hosts 5 other fragments. In each of those subfragments you can click on items. Then a different fragment should be presented that shows some more details. These fragments however should overlap the bottom navigation so that you have to navigate back before you can choose a different fragment in the bottom navigation bar.
As far as I can tell I need nested FragmentContainerViews. The MainActivity should be
<?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.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
The MainActivity should host the LoginFragment, SetupFragment and a MainFragment. In the MainFragment should be like this
<?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">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
<androidx.fragment.app.FragmentContainerView
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:layout_constrainedHeight="true"
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" />
</androidx.constraintlayout.widget.ConstraintLayout>
I would like to achieve all of this with a navigation graph. Do I have to use one graph or two for each FragmentContainerView? Then there is also the problem with the detail Fragments. If for example I have a HomeFragment as a child of the MainFragment which is a child of the MainActivity and in the HomeFragment the user clicks on for example a button to the SettingsFragment, this fragment should be displayed as a child of the MainActivity. How would I get this working? I've already had a look at this question but don't really understand how to implement it.
How to setup Multiple nested FragmentContainerViews with respective navigation graphs?
Could somebody maybe create a very simple implementation of this. I especially need help with the nested FragmentContainerViews, the BottomNavigationBar and the NavigationGraph. I've also come across the problem that the bottom navigation bar doesn't respond anymore.
Thanks for your help in advance. Please let me know if you need any more details.
1.way
val homeFragment = HomeFragment()
val listFragment = ListFragment()
val profileFragment = ProfileFragment()
nav_view.setOnItemSelectedListener {
when (it) {
R.id.homeFragment -> setCurrentFragment(homeFragment)
R.id.listFragment -> setCurrentFragment(listFragment)
R.id.profileFragment ->
setCurrentFragment(profileFragment)
}
return#setOnItemSelectedListener
}
private fun setCurrentFragment(fragment: Fragment) =
supportFragmentManager.beginTransaction().apply {
replace(R.id.nav_host_fragment_activity_main, fragment)
addToBackStack(null) //If you delete this, press the back button, it will not return to the previous fragment.
commit()
}
2.way
Navigation with
https://www.youtube.com/watch?v=DI0NIk-7cz8&t=527s&ab_channel=Stevdza-San
For some reason BottomNavigationView has a visual bug in layout. Does anyone know any way to fix it? The problem resolves after any button is clicked or after I minimize app and restore it.
This is how it is supposed to look:
Everything works when menu is inflated via XML.
<com.google.android.material.bottomnavigation.BottomNavigationView
android:layout_width="match_parent"
android:layout_height="56dp"
...
app:menu="#menu/bottom_navigation_4_game" />
When I added MenuItem programmatically:
navigationView.menu.clear()
navigationView.inflateMenu(R.menu.bottom_navigation4)
We may see in LayoutInspector, that there are actually 5 items, but two of them are overlayed and not seen:
The problem is probably in BottomNavigationMenuView. In LayoutInspector getWidth() returns 0. Invalidating views didn't help.
If you are trying to create dynamic BottomNavigationView with 2 different menu items set,
So instead of dynamically adding the menu item, use 2 different xml layouts (which have define 2 different app:menu property) and based on the conditions switch between them in your code then.
So, XML would look like this:
<BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar_height"
app:elevation="8dp"
app:itemIconTint="?attr/nav_item_color_state_red"
app:itemTextColor="?attr/nav_item_color_state_red"
app:labelVisibilityMode="labeled"
app:menu="#menu/bottom_navigation_1" />
<BottomNavigationView
android:id="#+id/bottom_navigation_mini_player"
android:layout_width="match_parent"
android:layout_height="#dimen/bottom_bar_height"
app:elevation="8dp"
app:itemIconTint="?attr/nav_item_color_state_red"
app:itemTextColor="?attr/nav_item_color_state_red"
app:labelVisibilityMode="labeled"
app:menu="#menu/bottom_navigation_2" />
I found a weird code block in the app, that was to blame. Turns out, that TransitionManager didn't end its transitions with ConstraintLayout. This code: updateConstraints {} was called immediately after dynamically changing BottomNavigationView, hence its child views transition was interrupted, I guess.
private fun updateConstraints(f: ConstraintSet.() -> Unit) {
TransitionManager.beginDelayedTransition(root)
val set = ConstraintSet().apply { clone(root) }
set.f()
set.applyTo(root)
}
I'm using Jetpack Navigation version 1.0.0-alpha04 with bottom navigation. It works but the navigation doesn't happen correctly. For example, if I have tab A and tab B and from tab A I go to Page C and from there I go to tab B and come back to tab A again, I will see root fragment in the tab A and not page C which does not what I expect.
I'm looking for a solution to have a different stack for each tab, so the state of each tab is reserved when I come back to it, Also I don't like to keep all this fragment in the memory since it has a bad effect on performance, Before jetpack navigation, I used this library https://github.com/ncapdevi/FragNav, That does exactly what, Now I'm looking for the same thing with jetpack navigation.
EDIT 2: Though still no first class support (as of writing this), Google has now updated their samples with an example of how they think this should be solved for now: https://github.com/googlesamples/android-architecture-components/tree/master/NavigationAdvancedSample
The major reason is you only use one NavHostFragment to hold the whole back stack of the app.
The solution is that each tab should hold its own back stack.
In your main layout, wrap each tab fragment with a FrameLayout.
Each tab fragment is a NavHostFragment and contains its own navigation graph in order to make each tab fragment having its own back stack.
Add a BottomNavigationView.OnNavigationItemSelectedListener to BottomNavigtionView to handle the visibility of each FrameLayout.
This also takes care of your "...I don't like to keep all this fragment in memory...", because a Navigation with NavHostFragment by default uses fragmentTransaction.replace(), i.e. you will always only have as many fragments as you have NavHostFragments. The rest is just in the back stack of your navigation graph.
Edit: Google is working on a native implementation https://issuetracker.google.com/issues/80029773#comment25
More in detail
Let's say you have a BottomNavigationView with 2 menu choices, Dogs and Cats.
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/dogMenu"
.../>
<item android:id="#+id/catMenu"
.../>
</menu>
Then you need 2 navigation graphs, say dog_navigation_graph.xml and cat_navigation_graph.xml.
The dog_navigation_graph might look like
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/dog_navigation_graph"
app:startDestination="#id/dogMenu">
</navigation>
and the corresponding for cat_navigation_graph.
In your activity_main.xml, add 2 NavHostFragments
<FrameLayout
android:id="#+id/frame_dog"
...>
<fragment
android:id="#+id/dog_navigation_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/dog_navigation_graph"
app:defaultNavHost="true"/>
</FrameLayout>
and underneath add the corresponding for your cat NavHostFragment. On your cat frame layout, set android:visibility="invisible"
Now, in your MainActivity's onCreateView you can
bottom_navigation_view.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.dogMenu -> showHostView(host = 0)
R.id.catMenu -> showHostView(host = 1)
}
return#setOnNavigationItemSelectedListener true
}
All that showHostView() is doing is toggling the visibility of your FrameLayouts that are wrapping the NavHostFragments. So make sure to save them in some way, e.g. in onCreateView
val hostViews = arrayListOf<FrameLayout>() // Member variable of MainActivity
hostViews.apply {
add(findViewById(R.id.frame_dog))
add(findViewById(R.id.frame_cat))
}
Now it's easy to toggle which hostViews should be visible and invisible.
The issue has been resolved by the Android team in the latest version 2.4.0-alpha01 multiple backstacks along with bottom navigation support is now possible without any workaround.
https://developer.android.com/jetpack/androidx/releases/navigation
First, I want to make an edit to #Algar's answer. The frame that you want to hide should have android:visibility="gone" instead of invisible. The reason for that in your main layout you would have something like this:
<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=".ui.activity.MainActivity">
<include
android:id="#+id/toolbar"
layout="#layout/toolbar_base" />
<FrameLayout
android:id="#+id/frame_home"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
>
<fragment
android:id="#+id/home_navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/home_nav" />
</FrameLayout>
<FrameLayout
android:id="#+id/frame_find"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:visibility="gone">
<fragment
android:id="#+id/find_navigation_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/find_nav" />
</FrameLayout>
...
</LinearLayout>
If you wrap your main in a LinearLayout, setting the frame to invisible still make that frame counts, so the BottomNavigation wont appear.
Second, you should create a NavHostFragment instance (ie: curNavHostFragment) to keep track of which NavHostFragment is being visible when a tab in BottomNavigation is clicked. Note: you may want to restore this curNavHostFragment when the activity is destroyed by configuration's changes. This is an example:
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//if this activity is restored from previous state,
//we will have the ItemId of botnav the has been selected
//so that we can set up nav controller accordingly
switch (bottomNav.getSelectedItemId()) {
case R.id.home_fragment:
curNavHostFragment = homeNavHostFragment;
...
break;
case R.id.find_products_fragment:
curNavHostFragment = findNavHostFragment;
...
break;
}
curNavController = curNavHostFragment.getNavController();
I have two activities with same bottom bar.
The problem is when i call to startActivity from Activity A to Activity B has some blink and is not looking so smooth.
for example what I want is like Activity with a container with two fragments and the activity has the bottom bar so this will not change the bottom bar.
I know Activity with Fragments can help me with that but is too complicated to change it on my project so is the last option for me.
I find one more option to do it with SharedElements transition but is supported only from api 21 (Lollipop).
This is my activities and I need the LinearLayout on bottom stay sticky when i change it to Activity B.
You can set up activity animations:
startActivity();
overridePendingTransition(R.anim.hold, R.anim.fade_in);
Please, refer to this answer: stackoverflow
you can remove the defulat transtion between activites.
try this under yourProject/res/values/styles.xml:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowAnimationStyle">#null</item>
</style>
If you want the same instance you will have to use fragments.
If not you could just put that LinearLayout to both layout files.
Which one do you want?
You need create a layout and include ex. bottombar.xml in layout folder and create the layout.
<include layout="#layout/bottombar"/>
if you dont want look delay in change you need use fragments.
To manage fragment, i recommended use FragNav
With this library manage fragments its very easy, remove animation is not solution to your problem
I have made an Activity with two fragments.
In Activity class ,I have write this code for commonBottomSheet :-
BottomSheetBehavior mBottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.bottom_pannel_layout));
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
mBottomSheetBehavior.setBottomSheetCallback(mBottomSheetCallback);
In Activity xml file in Co-ordinator layout I have included below layout :-
<include layout="#layout/bottom_sheet_pannel"/>
In CommonBottomSheetFragment, you can create your layout .
And my xml file (bottom_sheet_pannel) for bottomSheet is like this :-
<?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"
android:id="#+id/bottom_pannel_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_peekHeight="45dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<ImageView
android:id="#+id/grabber_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:src="#drawable/ic_vector_slider_grabber"
android:tint="#color/colorTint" />
<fragment
android:id="#+id/rf_common_details_fragment"
android:layout_marginTop="#dimen/margin_10"
android:name="com.fragment.CommonBottomSheetFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
you can change state of bottomSheet with below callBack :-
private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(View bottomSheet, int newState) {
// do what you want on state change
}
#Override
public void onSlide(View bottomSheet, float slideOffset) {
}
};
I created a collapsing transparent search bar using AppBarLayout, CollapsingToolbarLayout inside a CoordinatorLayout and a RecyclerView. It was a bit (lot) tricky to have the recyclerView appear behind the appBarLayout instead of below it ; but is working. My problem is that sometimes, the app bar does not re-enter when I scroll down. I simply stays invisible outside of the screen. Here is my layout :
<android.support.design.widget.CoordinatorLayout
android:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:expandedTitleMarginBottom="88dp">
<android.support.v7.widget.RecyclerView
android:id="#+id/services_recycler_view"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:behavior_overlapTop="88dp">
</android.support.v7.widget.RecyclerView>
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:background="#color/color_transparent"
app:elevation="0dp">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways"
android:fitsSystemWindows="false">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Search Location or Service"
android:id="#+id/button_search_bar"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
Any help on solving the not re-entering issue would be great.
A side problem, is that because I am using the app:behavior_overlapTop="88dp" to make recyclerView appear behind the app bar, the whole scrolling is a little odd : it starts by scrolling the appBar and then scrolls the recycler view. Any better solution is welcome.
EDIT :
I realized that the AppBar actually re-enter on scroll down but is invisible (I can click on it, I just can't see it). I figured I would share this new clue =)
It's not the answer i just want to confirmation that what you give, it will look like this?? on scroll up
means that Search button will come on top?
Edit:
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="false"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
add exitUntilCollapsed flag.
After a few hours of trial and error I managed to find a solution to your problem. You can set an OnScrollListener to your RecyclerView. Inside the listener you check if in onScrolled the first item of the RecyclerView is on screen.
If the first item is visible, first you change the visibility of your AppBarLayout to View.INVISIBLE and secondly you change it directly back to View.VISIBLE.
Your code may look like this (Kotlin) :
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
contentView(R.layout.my_activity)
val mAppbar = appbar
val mServices_recycler_view = services_recycler_view
//...
mServices_recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView : RecyclerView, newState : Int) {
super.onScrollStateChanged(recyclerView, newState)
}
override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) {
val layoutManager = LinearLayoutManager::class.java.cast(recyclerView.layoutManager)
if(layoutManager.findFirstVisibleItemPosition() == 0) {
mAppbar.visibility = View.INVISIBLE
mAppbar.visibility = View.VISIBLE
}
}
}
I am aware of the fact, that this is not very beautiful, but as long as it solves the issue I don't mind.
Additionally you want to improve the check if the first item of the RecyclerView is shown, so that it won't trigger everytime you scroll, even when it is only a little bit.
The layout you described in your question does not have to be changed.