Show/hide BottomNavigationView on scroll in CoordinatorLayout with AppBarLayout - android

I am trying to use both AppBarLayout and BottomNavigationLayout in a single CoordinatorLayout and I'm having difficulties hiding the BottomNavigationLayout as required by the material guideline.
I mean something like this:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="false">
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_insetEdge="top"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:layout_scrollFlags="scroll|enterAlways"/>
</android.support.design.widget.AppBarLayout>
<android.support.design.widget.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="bottom"
app:menu="#menu/menu_bottom_navigation"/>
<FrameLayout
android:id="#+id/content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
As you can see, I also have a FrameLayout that's used to contain a fragment with the actual content. Currently there are no default/built-in behaviors for the BottomNavigationView - neither for the view itself, nor for its siblings. The existing appbar_scrolling_view_behavior handles the content view in coordination with the appbar but ignores other siblings.
I am looking for a solution to hide and show both the appbar and the bottom navigation view on scroll.

After a day or two of searching I settled with a custom Behavior attached to the BottomNavigationView. Its main idea is to detect when the BottomNavigationView's sibling is scrolled so that it can hide the BottomNavigationView. Something like this:
public class BottomNavigationBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {
public BottomNavigationBehavior() {
super();
}
public BottomNavigationBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, BottomNavigationView child, View dependency) {
boolean dependsOn = dependency instanceof FrameLayout;
return dependsOn;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View target, int dx, int dy, int[] consumed) {
if(dy < 0) {
showBottomNavigationView(child);
}
else if(dy > 0) {
hideBottomNavigationView(child);
}
}
private void hideBottomNavigationView(BottomNavigationView view) {
view.animate().translationY(view.getHeight());
}
private void showBottomNavigationView(BottomNavigationView view) {
view.animate().translationY(0);
}
}
As you can see, I'm using simple ViewPropertyAnimator, obtained using the child views's animate method. This leads to a simple animation that doesn't really match the AppBarLayout's behavior but it's decent enough to look good and at the same time it's simple enough to implement.
I expect that at some point the Android team will add a default Behavior for the BottomNavigationView in the support library so I don't think it's reasonable to invest a lot more time to exactly duplicate the AppBarLayout's behavior.
edit (April 2018): see the comments section for a minor clarification about onStartNestedScroll and onNestedPreScroll and their new versions.

You can also use HideBottomViewOnScrollBehavior. This behavior works in basically the same way, but also handles cancelling any existing animations that are running which should be better for performance.

Related

Adding more than one behavior to a FloatingActionButton

duiring my last project I discovered the Android Material Design Library. It is pretty mighty and I had fun working with it. I added a custom behavior to my FloatingActionButton, so it disappears while scrolling downwards. Now I mentioned, if a SnackBar is shown the position of the FAB isn't handled automatically anymore.
After some debugging I found out, that setting the anchor to the recyclerView and adding the customBehaviour for scrolling the default behavior from the CoordinatorLayout depending the SnackBar is gone.
So I ask myself, can I add more then one Behaviour to my FAB? Or can I somehow tell it, that the defualt one should not be overwritten, but extended?
Or can I write more than one of those?
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
return dependency instanceof RecyclerView;
}
Okay, I found a method to realise the wished behavior, but it has more of an workaround than an answer.
If I add the Scrollbehavior programmatically in the Java Code like this:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
sendMailFAB.show();
}
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 0 && sendMailFAB.isShown())
sendMailFAB.hide();
}
});
And then delete the custom behavior and it's anchor in the .xml file, the CoordinatorLayout's default behavior handles the Snackbar and the onScrollListener the scroll behavior.
<?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/fragment_coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="de.flowment.designExample.StartActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/startRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
app:layout_anchorGravity="bottom|end"
android:src="#android:drawable/ic_dialog_email" />
<!-- DELETE THIS PART, BECAUSE IT'S NOT USED ANYMORE AND BLOCKS THE DEFAULT.
app:layout_anchor="#id/startRecyclerView"
app:layout_behavior="de.flowment.designExample.FABScrollBehavior" />-->
</android.support.design.widget.CoordinatorLayout>
So I achived two behavior, but like I said this is more of an workaround.

How to disable scrolling of AppBarLayout in CoordinatorLayout?

I have MapFragment with parallax effect inside AppBarLayout:
I want to disable scrolling on AppBarLayout, because it is not possible to move across map, since touch evenys on the map are always handled as scroll events.
I would like to handle collapsing of AppBarLayout by scrolling RecyclerView only, which is on the bottom of the screen.
This is my xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="#color/white"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:titleEnabled="false">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="parallax"
android:fitsSystemWindows="true">
<fragment
android:id="#+id/map"
android:name="com.androidmapsextensions.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="73dp"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light">
<include
android:id="#+id/search_bar"
layout="#layout/layout_searchbar" />
</android.support.v7.widget.Toolbar>
<View
android:id="#+id/toolbar_shadow"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_below="#id/search_bar"
android:layout_gravity="bottom"
android:background="#drawable/toolbar_dropshadow"
android:visibility="gone" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<RecyclerView
android:id="#+id/farm_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/transparent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Thank you for the response.
I'm not sure I got it, but I think you are looking for a DragCallback.
The DragCallback interface allows to choose whether the sibling scrolling view should be controlled by scrolls onto the AppBarLayout.
You can define one by calling:
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return false;
}
});
By always returning false, your scrolling view will not be controlled by the ABL any longer.
Note: before calling this you should check that ViewCompat.isLaidOut(appBarLayout), otherwise params.getBehavior() will return null.
Problem
AppBarLayout scrolls even if the scroll content fits the screen.
It is because by default we can drag AppBarLayout by touching & dragging AppBarLayout.
Solution
We will disable "Dragging" behaviour for AppBarLayout.
// Disable "Drag" for AppBarLayout (i.e. User can't scroll appBarLayout by directly touching appBarLayout - User can only scroll appBarLayout by only using scrollContent)
if (appBarLayout.getLayoutParams() != null) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior appBarLayoutBehaviour = new AppBarLayout.Behavior();
appBarLayoutBehaviour.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return false;
}
});
layoutParams.setBehavior(appBarLayoutBehaviour);
}
Reference
This is just extension of "natario's" accepted answer above.
https://developer.android.com/reference/android/support/design/widget/AppBarLayout.Behavior.DragCallback.html
You can accomplish this by defining a custom app:layout_behavior in xml. With this approach you don't have to worry about getting a reference to the LayoutParams and doing null checks.
<android.support.design.widget.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.yourcompany.FixedAppBarLayoutBehavior"
>
Then create a custom class that extends from AppBarLayout.Behavior.
public class FixedAppBarLayoutBehavior extends AppBarLayout.Behavior {
public FixedAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
setDragCallback(new DragCallback() {
#Override public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return false;
}
});
}
}
Updated with Kotlin version:
class FixedAppBarLayoutBehavior(context: Context, attrs: AttributeSet) : AppBarLayout.Behavior(context, attrs) {
init {
setDragCallback(object : DragCallback() {
override fun canDrag(appBarLayout: AppBarLayout): Boolean = false
})
}
}
So after two hours of trying I have found a solution, which is pretty simple. I just needed to extend CoordinatorLayout and override OnInterceptTouchEvent method so the class looks like this:
public class NonTouchableCoordinatorLayout extends CoordinatorLayout {
public NonTouchableCoordinatorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
}
you can use the NestedScrollView.setNestedScrollingEnabled(false) to disable the AppBarLayout nestScroll envent

Make FAB respond to Soft Keyboard show/hide changes

I've seen various posts about FABs responding to Snackbar popups at the bottom of the screen as well as scroll-sensitive FABs. But is there some implementation of FloatingActionButton.Behavior (or similar) to move the FAB above the keyboard when it pops up?
Right now, the keyboard covers the FAB when I click for example in an EditText box. My goal is to animate the FAB so it is always visible, independent of the keyboard status.
EDIT: Both android:windowSoftInputMode="adjustResize" and ...="adjustPan" won't change anything. Well, adjustResize resizes the underlying layout (which is in my case a map) but the FAB doesn't move.
Hi there i know it's old but for future or current readers/searchers and also the thread maker, he hasn't found answer yet. This is how i am having this behaviour in my app.
Fab hides on RecyclerView scroll, goes up when snack bar pops out, if fab is not shown and snackbar popus up and if you scroll then still Fab will be shown top of snack bar and will move down when SB disappears and last with keypad if it opens Fab will be pushed up. (sorry, i had to write coz i don't know how to give gif with eclipse emulator)
Image
Layout
<android.support.v4.widget.DrawerLayout 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/layout_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/GrayGood"
android:clipToPadding="false"
android:fitsSystemWindows="true" >
<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/layout_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<android.support.design.widget.AppBarLayout
android:id="#+id/layout_appLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark" >
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap"
android:background="?attr/colorPrimary" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerViewMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:clipToPadding="false" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/floating_action_button_margin"
app:layout_behavior="com.faisal.cvcd.anim.ScrollingFABAnimation"
android:src="#drawable/ic_fab"
android:tint="#color/White"
app:backgroundTint="#color/colorPrimary"
app:borderWidth="0dp"
app:elevation="6dp"
app:fabSize="normal" />
</android.support.design.widget.CoordinatorLayout>
</android.support.v4.widget.DrawerLayout>
As you can see i am using FabAnimation class to override some of its default methods.
ScrollingFABAnimation
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
public class ScrollingFABAnimation extends
CoordinatorLayout.Behavior<FloatingActionButton> {
public ScrollingFABAnimation(Context context, AttributeSet attrs) {
super(context, attrs);
}
//For SnackBar to push up the Floating Button when it pops up
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
child.setTranslationY(translationY);
return true;
}
//For FAB to hide or show on scroll
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target,
int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child,
directTargetChild, target, nestedScrollAxes);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed,
int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide();
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
}
I had the same problem, i tried to add a ScrollView inside my root view but it did not works because the content doesn't exceed the display's height (the fab react as expected in that case).
So i tried android:windowSoftInputMode="adjustResize" and it works for me.
For informations here's what my layout xml looks like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<ManyViewsExceptScrollViews/>
<android.support.design.widget.FloatingActionButton
[..]
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
it's a Fragment inflated in an activity (in a FrameLayout that match_parent the root view) for which i added android:windowSoftInputMode="adjustResize" in the manifest.

How to make the Toolbar snap into view or out of view when using Google Design Library?

I am trying to achieve an effect like WhatsApp has, where the Toolbar (when scrolled) will clip into view magnetlike, or out of view magnetlike.
What I have im my MainActivity XML:
DrawerLayout - Base Layout
CoordinatorLayout - Layout for the Appbar and Toolbar and Tabs
AppBarLayout - For holding Toolbar and Tabs
Toolbar - has THIS flag: app:layout_scrollFlags="scroll|enterAlways"
SlidingTabLayout - Displays tabs
ViewPager - For tabs
RecyclerView - For coordinatorlayout
Now dont get me wrong, it works, when I scroll down the toolbar gets pushed out of view but say I stop scrolling halfway, then the toolbar just sits there half hidden out of view and the other half in view..
How can I approach solving this problem, as I want it to either snap out of view or into view.
This feature has been added in 23.1.0 version of android support library.
From release notes:
Added edge snapping support to the AppBarLayout class by adding the
SCROLL_FLAG_SNAP constant. When scrolling ends, if the view is only
partially visible, the view is snapped and scrolled to its closest
edge.
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways|snap" />
-----
-----
For more info: http://android-developers.blogspot.in/2015/10/android-support-library-231.html
EDIT: as of support 23.1.0 this is no longer needed. See this answer instead.
One possible way to solve this is customizing the Behavior set to your AppBarLayout.
<android.support.design.widget.AppBarLayout
app:layout_behavior="com.myapp.AppBarLayoutSnapBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content">
...
Your AppBarLayoutSnapBehavior would change the default behavior of AppBarLayout.Behavior, by adding the snap logic when the scroll stops.
Hopefully, the code below is self explanatory.
package com.myapp;
public class AppBarLayoutSnapBehavior extends AppBarLayout.Behavior {
private ValueAnimator mAnimator;
private boolean mNestedScrollStarted = false;
public AppBarLayoutSnapBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View directTargetChild, View target, int nestedScrollAxes) {
mNestedScrollStarted = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
if (mNestedScrollStarted && mAnimator != null) {
mAnimator.cancel();
}
return mNestedScrollStarted;
}
#Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
if (!mNestedScrollStarted) {
return;
}
mNestedScrollStarted = false;
int scrollRange = child.getTotalScrollRange();
int topOffset = getTopAndBottomOffset();
if (topOffset <= -scrollRange || topOffset >= 0) {
// Already fully visible or fully invisible
return;
}
if (topOffset < -(scrollRange / 2f)) {
// Snap up (to fully invisible)
animateOffsetTo(-scrollRange);
} else {
// Snap down (to fully visible)
animateOffsetTo(0);
}
}
private void animateOffsetTo(int offset) {
if (mAnimator == null) {
mAnimator = new ValueAnimator();
mAnimator.setInterpolator(new DecelerateInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
setTopAndBottomOffset((int) animation.getAnimatedValue());
}
});
} else {
mAnimator.cancel();
}
mAnimator.setIntValues(getTopAndBottomOffset(), offset);
mAnimator.start();
}
}
The only thing is, the scroll view (in my case a RecyclerView) snaps along with the Toolbar. I actually like it this way, but I'm not sure that's what you want.
I just hided action bar layout in main activity and set span for CollapsingToolbarLayout.
it works for me.
in main activity
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().hide();
CollapsingToolbarLayout collapsingToolbar =
(CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
collapsingToolbar.setTitle("Name");
loadBackdrop();
and layout_activity_main
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="#dimen/detail_backdrop_height"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|snap"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp">
<ImageView
android:id="#+id/backdrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

Show view when toolbar collapses

I have an activity with a CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout and Toolbar. So, basically, a view that collapses when scrolling a RecyclerView.
What I need to do is to show a custom view when the view of the expanded layout is hidden due to collapsing.
This is my layout:
<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:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="192dp"
android:fitsSystemWindows="true"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="#2196F3"
app:expandedTitleMarginBottom="32dp"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<include
android:id="#+id/header"
layout="#layout/header_big_first_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"/>
<android.support.v7.widget.CollapsingToolbarLayout
android:id="#+id/anim_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello!"/>
</android.support.v7.widget.CollapsingToolbarLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/categories_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>
In the end, when the toolbar is expanded the view loaded with the element is shown. When it is collapsed it doesn't . When it disappears the TextView inside Toolbarshould be shown. Currently it shows all the time.
I've been looking in the events of CollapsingToolbarLayout to add a listener when it changes size so I can check if that is smaller than a value and show that view.
This can be kind of tricky to explain but I believe I made myself clear. I've been googling around and cannot find anyone trying to do the same.
Taking a look at the CollapsingToolbarLayout source, the content scrim animations are triggered via an OnOffsetChangedListener on the AppBarLayout. So you could add another one to trigger alpha animations on your text view:
mListener = new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
if(collapsingToolbar.getHeight() + verticalOffset < 2 * ViewCompat.getMinimumHeight(collapsingToolbar)) {
hello.animate().alpha(1).setDuration(600);
} else {
hello.animate().alpha(0).setDuration(600);
}
}
};
appBar.addOnOffsetChangedListener(mListener);
Instead of trying to replicate when the scrim is supposed to show (collapsed) or not (expanded), a better way is to override the setScrimsShown method, which is called every time inside the CollapsingToolbarLayout's onOffsetChanged, and add a listener to it like this:
public class CollapsingToolbarLayoutWithScrimListener extends CollapsingToolbarLayout {
public CollapsingToolbarLayoutWithScrimListener(Context context) {
super(context);
}
public CollapsingToolbarLayoutWithScrimListener(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CollapsingToolbarLayoutWithScrimListener(Context context, AttributeSet attrs, int defStyleAttr) {
super(context,attrs,defStyleAttr);
}
private ScrimListener scrimListener = null;
#Override
public void setScrimsShown(boolean shown, boolean animate) {
super.setScrimsShown(shown, animate);
if (scrimListener != null)
scrimListener.onScrimShown(shown);
}
public void setScrimListener(ScrimListener listener) {
scrimListener = listener;
}
public interface ScrimListener {
void onScrimShown(boolean shown);
}
}

Categories

Resources