I have collapsing toolbar and some layout in it I want to collapse. To prevent view going under status bar, I use system insets to set margin for collapsing toolbar. I extracted AppBarLayout to separate file and included it inside CoordinatorLayout:
<com.google.android.material.appbar.AppBarLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/app_white"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:background="#color/colorToolbar"
android:elevation="4dp"
android:focusable="true"
android:focusableInTouchMode="true"
app:contentInsetStartWithNavigation="0dp"
app:layout_collapseMode="pin">
...
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.card.MaterialCardView
android:id="#+id/bonuses_widget"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="14dp"
app:cardElevation="3dp"
app:layout_collapseMode="parallax">
...
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
Margins for MaterialCardView are set from code because layout doesn't support addition. I add margins for collapsing toolbar using insets:
ViewCompat.setOnApplyWindowInsetsListener(action_bar) { _, insets ->
collapsing_toolbar.setMarginTop(insets.systemWindowInsetTop)
insets
}
This works well when collapsing toolbar is expanded, but in collapsed state toolbar goes under status bar:
I have read this question, have implemented AppBarStateChangeListener and use it like this:
action_bar.addOnOffsetChangedListener(object : AppBarStateChangeListener() {
override fun onStateChanged(appBarLayout: AppBarLayout, state: State) {
if (state == State.COLLAPSED) {
ViewCompat.setOnApplyWindowInsetsListener(collapsing_toolbar) { _, insets ->
toolbar.setMarginTop(insets.systemWindowInsetTop)
insets
}
}
if (state == State.EXPANDED) {
ViewCompat.setOnApplyWindowInsetsListener(collapsing_toolbar) { _, insets ->
toolbar.setMarginTop(0)
insets
}
}
}
})
This didn't help, as I can understand so far, status bar's insets is already handled by action bar and collapsing toolbar has nothing to handle.
I also tried to margin to toolbar at the same time with collapsing toolbar, it worked but leaded to another problem:
After all I tried to remove insets and just set android:fitsSystemWindows="true" to action bar, this fixes problem with toolbar under status bar, but status bar unexpectedly gets strange color (purple) which isn't represented in app colors.
Hope someone knows hot to handle with insets properly in this case.
You can try calling the setSupportActionBar() in your Fragment, because that works for me:
(requireActivity() as AppCompatActivity).apply {
//supportActionBar?.hide() //may need this to hide the Activity actionBar
setSupportActionBar(binding.toolbar)
}
setHasOptionsMenu(true) //don't forget this
Demo: https://youtu.be/kmnsep_V-jE
Eventually I found suitable solution:
ViewCompat.setOnApplyWindowInsetsListener(action_bar) { view, insets ->
view.updatePadding(top = insets.systemWindowInsetTop)
insets
}
There is still problem with white background under status bar (I need it to be dark as on left screenshots), but initial problem is solved.
Related
I am having an issue with my FabCradleMargin becoming less, almost flat, inside my Bottom App Bar when navigating through my app and scrolling up/down while hideonScroll is set to true. When the BottomAppBar hides from the screen, it returns resized under the Floating action Button. Must be a glitch in the new Android Material Components. Has anyone else been experiencing this issue. If so, what suggestions do you have to fixing it.
and
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
app:elevation="4dp"
app:fabAlignmentMode="center"
app:fabCradleRoundedCornerRadius="2dp"
app:hideOnScroll="true"
app:layout_scrollFlags="scroll|enterAlways"
app:navigationIcon="#drawable/ic_action_list" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#color/blue500"
app:fabSize="normal"
app:layout_anchor="#+id/bar"
app:tint="#color/white"
app:layout_anchorGravity="right"
app:srcCompat="#drawable/ic_select_camera" />
I stumbled upon this problem as well. In my case it depended on the way I tried to hide the BottomAppBar and the FloatingActionButton. This is what I had first (Kotlin):
private fun showBottomNavigationBar(barVisibility: Boolean, fabVisibility: Boolean) {
navView.visibility = if (barVisibility) BottomAppBar.VISIBLE else BottomAppBar.GONE
fab.visibility = if (fabVisibility) FloatingActionButton.VISIBLE else FloatingActionButton.GONE
}
And this is what fixed it:
private fun showBottomNavigationBar(barVisibility: Boolean, fabVisibility: Boolean) {
navView.visibility = if (barVisibility) BottomAppBar.VISIBLE else BottomAppBar.GONE
if (fabVisibility) fab.show() else fab.hide()
}
So instead of hiding the FloatingActionButton with the visibility property, I used the hide() and show() methods of the FloatingActionButton.
I have a collapsing toolbar with a pinned toolbar, I want the collapsing toolbars free space to be allowed to scroll freely but the pinned toolbar to snap open or closed, my question is can this be achieved with scroll flags or will i need to create a custom layout behaviour or do some disabling and enabling of the flags based on the toolbars offset so to illustrate what i want here are some images,
Id like to allow this example of it being fully expanded
This would be an example of the scrolling freely free space (no snapping)
This would be the collapsing toolbar fully scrolled and the pinned toolbar
But I never want to allow this
This is the pinned toolbar also being allowed to scroll freely (as its a child of the collapsing toolbar) I want just this toolbar to have a scroll flag of snap but in practice this doesn't work the toolbars flags are ignored
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants"
app:titleEnabled="false"
app:title=""
app:titleTextColor="#android:color/transparent"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#drawable/squareangle"
app:titleTextColor="#android:color/transparent"
app:title=""
android:elevation="#dimen/large_margin_32dp"
android:layout_gravity="top"
android:minHeight="?attr/actionBarSize"
app:elevation="#dimen/large_margin_32dp"
app:layout_scrollFlags="snap"
app:layout_collapseMode="pin"/>
You can set scroll flags with
toolbarParams.setScrollFlags(SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS)
or by shifting as you can see here, this is a sample playground to test Toolbar features.
Also you check AppbarLayout offset using
appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
//Check if the view is collapsed
if (abs(verticalOffset) >= appbar.totalScrollRange) {
collapsingToolbar.title = "Collapsed"
} else {
collapsingToolbar.title = ""
}
// Change flags based on position of offset
})
I used this to create scrolling behavior over appbar for RecyclerView as can be seen here
Thanks to Thracian i ended up with something like this
private fun handleAppBarSnapFlag() {
binding.appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
if (abs(verticalOffset) >= appBarLayout.totalScrollRange - getToolbarHeight()) {
val toolbarParams = binding.collapsingToolbar.layoutParams as AppBarLayout.LayoutParams
toolbarParams.scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED or SCROLL_FLAG_SNAP
} else {
val toolbarParams = binding.collapsingToolbar.layoutParams as AppBarLayout.LayoutParams
toolbarParams.scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED
}
})
}
I need your help to understand a strange behavior. When I set the fitsSystemWindows property to 'true', the navigation bar hides some part of my layout, see the image below :
When I set to false, I have this behavior (it's OK) :
When I read the Android documentation and many posts on Stackoverflow, I understand it should be the exact opposite of this behaviour : https://developer.android.com/reference/android/view/View#attr_android:fitsSystemWindows.
The first case with fitsSystemWindows='true' should be OK and the second case should be hidden by the navigation bar, am I wrong ?
Could someone explain me what's happened ? My targetVersionSdk is 29 and I tested it on many versions (Android 6,7 10 and 11). Maybe it's specific to CoordinatorLayout ? Thanks for your explanations :)
Here is my xml layout :
<?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/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="[true or false]">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
</com.google.android.material.appbar.AppBarLayout>
[...]
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginBottom="#dimen/activity_vertical_margin"
android:layout_marginEnd="#dimen/activity_vertical_margin"
android:src="#drawable/ic_arrow_forward_white_24dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
I think the view is working as expected. First you need to understand the insets and how it's passed around. The default behavior of fitsSystemWindow is to consume all the insets and apply them as padding. But ViewGroups like CoordinatorLayout, DrawerLayout override this behavior.
Here is the snippet of the code in CoordinatorLayout that overrides the behavior.
private void setupForInsets() {
if (Build.VERSION.SDK_INT < 21) {
return;
}
if (ViewCompat.getFitsSystemWindows(this)) {
if (mApplyWindowInsetsListener == null) {
mApplyWindowInsetsListener =
new androidx.core.view.OnApplyWindowInsetsListener() {
#Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
return setWindowInsets(insets);
}
};
}
ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} else {
ViewCompat.setOnApplyWindowInsetsListener(this, null);
}
}
As you can see applying fitsSystemWindow in CoordinatorLayout causes it to render the contents under the system UI. What you need to do is to add the insets provided by the system and apply it as margin or padding to the top and bottom views.
You can use setOnApplyWindowInsetsListener() to listen for insets and apply it. Let's say you havebottomNav as bottom view then you can do something like this to account for the bottom inset.
ViewCompat.setOnApplyWindowInsetsListener(bottomNav) { view, insets ->
bottomNav.updatePadding(bottom = insets.systemWindowInsetBottom)
insets
}
You can learn more about insets in this blog post.
I am having an issue with my FabCradleMargin becoming less, almost flat, inside my Bottom App Bar when navigating through my app and scrolling up/down while hideonScroll is set to true. When the BottomAppBar hides from the screen, it returns resized under the Floating action Button. Must be a glitch in the new Android Material Components. Has anyone else been experiencing this issue. If so, what suggestions do you have to fixing it.
and
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bar"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="bottom"
app:elevation="4dp"
app:fabAlignmentMode="center"
app:fabCradleRoundedCornerRadius="2dp"
app:hideOnScroll="true"
app:layout_scrollFlags="scroll|enterAlways"
app:navigationIcon="#drawable/ic_action_list" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#color/blue500"
app:fabSize="normal"
app:layout_anchor="#+id/bar"
app:tint="#color/white"
app:layout_anchorGravity="right"
app:srcCompat="#drawable/ic_select_camera" />
I stumbled upon this problem as well. In my case it depended on the way I tried to hide the BottomAppBar and the FloatingActionButton. This is what I had first (Kotlin):
private fun showBottomNavigationBar(barVisibility: Boolean, fabVisibility: Boolean) {
navView.visibility = if (barVisibility) BottomAppBar.VISIBLE else BottomAppBar.GONE
fab.visibility = if (fabVisibility) FloatingActionButton.VISIBLE else FloatingActionButton.GONE
}
And this is what fixed it:
private fun showBottomNavigationBar(barVisibility: Boolean, fabVisibility: Boolean) {
navView.visibility = if (barVisibility) BottomAppBar.VISIBLE else BottomAppBar.GONE
if (fabVisibility) fab.show() else fab.hide()
}
So instead of hiding the FloatingActionButton with the visibility property, I used the hide() and show() methods of the FloatingActionButton.
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.