I have a class
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
super();
}
public ScrollAwareFABBehavior() {
super();
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,final View directTargetChild, final View target, final int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,final View target, final int dxConsumed, final int dyConsumed,final int dxUnconsumed, final 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();
}
}
}
the problem is that onNestedScroll is called only once when I scroll up a recyclerview, so the fab is hiding and never shows again. Here is layout I am using
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/refresh_layout"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/list"
app:layoutManager="#string/vertical_linear_layout_manager"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.v4.widget.SwipeRefreshLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/add"
android:layout_width="wrap_content"
app:layout_behavior="mypackagename.util.ScrollAwareFABBehavior"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
app:useCompatPadding="true"
android:layout_margin="16sp"
android:src="#drawable/ic_add"/>
</android.support.design.widget.CoordinatorLayout>
My support libraries version is 25.1.0
I solved the problem changing the visibility from GONE to INVISIBLE with the following code.
#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(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onShown(FloatingActionButton fab) {
super.onShown(fab);
}
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
fab.setVisibility(View.INVISIBLE);
}
});
} else if (dyConsumed <= 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
I have just answered
to absolutely the same problem in another post, check it.
Speaking shortly, use the following:
compile 'com.android.support:design:25.0.1'
#Override
public void onNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull FloatingActionButton child, #NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
fab.setVisibility(View.INVISIBLE);
}
});
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
}
Do not set the visibility of FAB to GONE in child.hide() - use INVISIBLE instead. From 25.1.0, the scroll events are not passed on to views whose visibility is GONE
For me i replace child.hide() with ((View)child).setVisibility(View.INVISIBLE).
Related
I checked this stackoverflow question cause it's very similar, but the Google's bug have been fixed in current versions, but I still having the problem.
I have an RecyclerView inside a NestedScrollView, after NestedScrollView scrolled if I click on item inside RecyclerView, onClick method does not work propertly.
Can anyone help me? Thanks
Okey, I found the solution here, we need:
public class FixAppBarLayoutBehavior extends AppBarLayout.Behavior {
public FixAppBarLayoutBehavior() {
super();
}
public FixAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target,
int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type);
stopNestedScrollIfNeeded(dyUnconsumed, child, target, type);
}
#Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
stopNestedScrollIfNeeded(dy, child, target, type);
}
private void stopNestedScrollIfNeeded(int dy, AppBarLayout child, View target, int type) {
if (type == ViewCompat.TYPE_NON_TOUCH) {
final int currOffset = getTopAndBottomOffset();
if ((dy < 0 && currOffset == 0)
|| (dy > 0 && currOffset == -child.getTotalScrollRange())) {
ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH);
}
}
}
}
and, in our AppBarLayout:
<android.support.design.widget.AppBarLayout>
...
app:layout_behavior="your.package.FixAppBarLayoutBehavior"
...
</android.support.design.widget.AppBarLayout>
Your RecyclerView shouldn't permit nested scrolling, so it must have nestedScrollingEnabled="false"
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"/>
I have an activity in which there is a ViewPager and the BottomNavigationView at the bottom of the page. The ViewPager consists of few fragments in it.
Below is the layout for the activity
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="#layout/toolbar_with_tab_strip_layout" />
<android.support.design.widget.CoordinatorLayout
android:id="#+id/coordinatorlayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.tf.eros.faythTv.widgets.NonScrollingViewPager
android:id="#+id/home_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1" />
<android.support.design.widget.BottomNavigationView
android:id="#+id/bottom_navigation_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#color/white"
app:elevation="16dp"
app:itemIconTint="#drawable/home_bottombar_icon_color_selector"
app:itemTextColor="#drawable/home_bottombar_icon_color_selector"
app:menu="#menu/bottom_navigation_main" />
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>
And I have also added a BottomNavigationViewBehavior class that will be handling the hiding/unhiding of the BottomNavigationView on scrolling of the fragments in the viewpager
public class BottomNavigationViewBehavior extends CoordinatorLayout.Behavior<BottomNavigationView> {
private int height;
#Override
public boolean onLayoutChild(CoordinatorLayout parent, BottomNavigationView child, int layoutDirection) {
height = child.getHeight();
return super.onLayoutChild(parent, child, layoutDirection);
}
#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) {
slideDown(child);
} else if (dy < 0) {
slideUp(child);
}
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, BottomNavigationView child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
if (dyConsumed > 0) {
slideDown(child);
} else if (dyConsumed < 0) {
slideUp(child);
}
}
private void slideUp(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(0).setDuration(200);
}
private void slideDown(BottomNavigationView child) {
child.clearAnimation();
child.animate().translationY(height).setDuration(200);
}
}
In the Activity I have added these two lines in the onCreate Method
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) bottomNavigationView.getLayoutParams();
layoutParams.setBehavior(new BottomNavigationViewBehavior());
The aim is to hide the BottomNavigationView when the page is scrolled down(dy > 0) and make it visible when it is scrolled up (dy < 0).
Now the issue with the code is the hiding and unhiding of the BottomNavigationView is not spontaneous with the scrolling. Sometimes it doesn't hide at all while scrolling.
I'm trying to hide two FAB on Nested Scroll scrolling. I've tried to implement my own FAB behavior but it did't work. FAB's don't react on scrolling at all. Note: I deleted some part of layout to fit the post.
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:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="ru.berezin.maxim.im.budget_v3.userinterface.StartPage">
<android.support.design.widget.AppBarLayout
android:id="#+id/appcollapse"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:id="#+id/testingscroll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
app:behavior_overlapTop="30dp"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<!--content including-->
<include layout="#layout/start_content_layout"/>
</android.support.v4.widget.NestedScrollView>
<android.support.design.widget.FloatingActionButton
android:id="#+id/start_page_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="#drawable/plus"
app:layout_anchor="#id/testingscroll"
app:layout_anchorGravity="end|bottom"
app:layout_behavior="ru.berezin.maxim.im.budget_v3.FabOnScroll"
/>
<View
android:id="#+id/dummy"
android:layout_width="1dp"
android:layout_height="16dp"
app:layout_anchor="#id/start_page_fab"
app:layout_anchorGravity="top|right|end"
/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/account_det_add_transfer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|top"
android:layout_margin="16dp"
android:src="#drawable/minus"
app:layout_anchor="#id/dummy"
app:layout_anchorGravity="top|right|end"
app:layout_behavior="ru.berezin.maxim.im.budget_v3.FabOnScroll"
/>
</android.support.design.widget.CoordinatorLayout>
FabOnScroll class
public class FabOnScroll extends FloatingActionButton.Behavior {
public FabOnScroll(Context context, AttributeSet attrs) {
super();
}
#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);
//child -> Floating Action Button
if (dyConsumed > 0) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
int fab_bottomMargin = layoutParams.bottomMargin;
child.animate().translationY(child.getHeight() + fab_bottomMargin).setInterpolator(new LinearInterpolator()).start();
} else if (dyConsumed < 0) {
child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
}
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
}
So, I fixed it. Not exactly what I wanted, but it's working. I dont have any ideas why, but for some reason dyConsumed is always equals 0. But I noticed that dyUnconsumed is changing while I scroll. So I've just add dxUnconsumed check to my If/else statment and it's worked. Can someone explain why dyConsumed equals 0?
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener()
{
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy)
{
if (dy > 0 ||dy<0 && fab.isShown())
{
fab.hide();
}
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState)
{
if (newState == RecyclerView.SCROLL_STATE_IDLE)
{
fab.show();
}
super.onScrollStateChanged(recyclerView, newState);
}
});
I know it's a late answer but my solution is to use dyUnconsumed instead of dyConsumed inside the if statement and it worked very well with me
if (dyUnconsumed > 0 && child.getVisibility() == View.VISIBLE) {
child.hide(new FloatingActionButton.OnVisibilityChangedListener() {
#Override
public void onHidden(FloatingActionButton fab) {
super.onHidden(fab);
child.setVisibility(View.INVISIBLE);
}
});
} else if (dyUnconsumed <0 && child.getVisibility() != View.VISIBLE) {
child.show();
}
Im using this great library to create a FloatingActionMenu. I use it inside a coordinator layout:
<android.support.design.widget.CoordinatorLayout
android:id="#+id/evaluations_list_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/evaluations_list_textview_time"
android:layout_below="#+id/evaluations_list_textview_name"
android:importantForAccessibility="no">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/evaluations_list_swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no">
<android.support.v7.widget.RecyclerView
android:id="#+id/evaluations_list_row_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:importantForAccessibility="no"
android:padding="10dp"
android:textColor="#color/textcolorprimary" />
</android.support.v4.widget.SwipeRefreshLayout>
<com.github.clans.fab.FloatingActionMenu
android:id="#+id/evaluations_list_floating_action_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:scaleType="fitXY"
android:src="#drawable/vector_drawable_ic_add_white"
app:layout_anchor="#id/evaluations_list_row_parent"
app:layout_anchorGravity="bottom|right|end"
app:layout_behavior="my.project.views.buttons.ScrollFAMBehaviour"
app:menu_animationDelayPerItem="50"
app:menu_colorNormal="#color/primaryColorDark"
app:menu_colorPressed="#color/accentColor"
app:menu_fab_size="normal"
app:menu_openDirection="up">
<com.github.clans.fab.FloatingActionButton
android:id="#+id/evaluations_list_floating_action_button_filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:scaleType="fitXY"
android:src="#drawable/vector_drawable_ic_check_white"
app:fab_colorNormal="#color/primaryColorDark"
app:fab_colorPressed="#color/accentColor"
app:fab_label="#string/fab_filter"
app:fab_size="mini" />
<com.github.clans.fab.FloatingActionButton
android:id="#+id/evaluations_list_floating_action_button_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:scaleType="fitXY"
android:src="#drawable/vector_drawable_ic_search_white"
app:fab_colorNormal="#color/primaryColorDark"
app:fab_colorPressed="#color/accentColor"
app:fab_label="#string/fab_search"
app:fab_size="mini" />
</com.github.clans.fab.FloatingActionMenu>
</android.support.design.widget.CoordinatorLayout>
Now i want to create a CoordinatorLayout.Behavior to hide the complete menu when the user is scrolling down and i want it to show up again when the user is scrolling up.
Bevor i used the FloatingActionMenu i used the classic FloatingActionButton. For that it was easily possible to achieve what i want using the following code:
public class ScrollFABBehaviour extends FloatingActionButton.Behavior {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private boolean isAnimatingOut = false;
public ScrollFABBehaviour(Context context, AttributeSet attrs) {
super();
}
#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 && !this.isAnimatingOut && child.getVisibility() == View.VISIBLE) {
this.animateOut(child);
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
this.animateIn(child);
}
}
private void animateOut(final FloatingActionButton button) {
ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer().setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
ScrollFABBehaviour.this.isAnimatingOut = true;
}
public void onAnimationCancel(View view) {
ScrollFABBehaviour.this.isAnimatingOut = false;
}
public void onAnimationEnd(View view) {
ScrollFABBehaviour.this.isAnimatingOut = false;
view.setVisibility(View.GONE);
}
}).start();
}
private void animateIn(FloatingActionButton button) {
button.setVisibility(View.VISIBLE);
ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F).setInterpolator(INTERPOLATOR).withLayer().setListener(null).start();
}
}
Unfortunately i cant use this code on the FloatingActionMenu because it is not a FloatingActionButton and also does not inherit it. It is a custom ViewGroup.
How could i create such a behavior for the FloatingActionMenu?
This behavior is really obtained by creating a CoordinatorLayout.Behavior<View>. So instead of extending from FloatingActionButton.Behavior extend from CoordinatorLayout.Behavior<FloatingActionMenu> directly.
Here is the equivalent code that will work with the FloatingActionMenu.
public class ScrollFAMBehaviour extends CoordinatorLayout.Behavior<FloatingActionMenu>{
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private boolean isAnimatingOut = false;
public ScrollFAMBehaviour(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionMenu 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, FloatingActionMenu child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && !this.isAnimatingOut && child.getVisibility() == View.VISIBLE) {
this.animateOut(child);
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
this.animateIn(child);
}
}
private void animateOut(final FloatingActionMenu menu) {
ViewCompat.animate(menu).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer().setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
ScrollFAMBehaviour.this.isAnimatingOut = true;
}
public void onAnimationCancel(View view) {
ScrollFAMBehaviour.this.isAnimatingOut = false;
}
public void onAnimationEnd(View view) {
ScrollFAMBehaviour.this.isAnimatingOut = false;
view.setVisibility(View.GONE);
}
}).start();
}
private void animateIn(FloatingActionMenu menu) {
menu.setVisibility(View.VISIBLE);
ViewCompat.animate(menu).scaleX(1.0F).scaleY(1.0F).alpha(1.0F).setInterpolator(INTERPOLATOR).withLayer().setListener(null).start();
}
}
My activity contains 2 AppBarLayouts in a CoordinatorLayout, one on the top of screen, and the other bottom. I want to make both of the 2 AppBarLayouts hide on scroll of a RecyclerView.
When there is only the top one, it's easy to make it hide on scroll by adding app:layout_scrollFlags="scroll|enterAlways" to the Toolbar, and app:layout_behavior="#string/appbar_scrolling_view_behavior" to the container of RecyclerView.
When there is only the bottom AppBarLayout which hide on scroll, it was realized by making a custom behavior BottomAppBarLayoutBehavior extends AppBarLayout.Behavior.
However, when both of them are made hide on scroll, they succeed to hide but the RecyclerView shakes on scroll. And the area of top Toolbar leaves empty white space after hiding.
Below is the xml:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/topToolbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:layout_width="fill_parent"
android:orientation="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:id="#+id/RecyclerViewContainer"
android:layout_height="fill_parent"/>
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_gravity="bottom"
app:layout_behavior="myPackage.BottomAppBarLayoutBehavior">
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="match_parent">
<!-- some Views -->
</LinearLayout>
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>
Below is the code of BottomAppBarLayoutBehavior:
public class BottomAppBarLayoutBehavior extends AppBarLayout.Behavior {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private boolean mIsAnimatingOut = false;
public BottomAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final AppBarLayout child,
final View directTargetChild, final View target, final int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
#Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final AppBarLayout child,
final View target, final int dxConsumed, final int dyConsumed,
final int dxUnconsumed, final int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
animateOut(child);
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
animateIn(child);
}
}
private void animateOut(final AppBarLayout appBarLayout) {
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(appBarLayout).translationY(168F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer()
.setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
BottomAppBarLayoutBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationCancel(View view) {
BottomAppBarLayoutBehavior.this.mIsAnimatingOut = false;
}
public void onAnimationEnd(View view) {
BottomAppBarLayoutBehavior.this.mIsAnimatingOut = false;
view.setVisibility(View.GONE);
}
}).start();
} else {
Animation anim = AnimationUtils.loadAnimation(appBarLayout.getContext(), R.anim.fab_out);
anim.setInterpolator(INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
BottomAppBarLayoutBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
BottomAppBarLayoutBehavior.this.mIsAnimatingOut = false;
appBarLayout.setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(final Animation animation) {
}
});
appBarLayout.startAnimation(anim);
}
}
private void animateIn(AppBarLayout appBarLayout) {
appBarLayout.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(appBarLayout).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
.start();
} else {
Animation anim = AnimationUtils.loadAnimation(appBarLayout.getContext(), R.anim.fab_in);
anim.setDuration(200L);
anim.setInterpolator(INTERPOLATOR);
appBarLayout.startAnimation(anim);
}
}
}
I have found the solution by myself. The AppBarLayout wrapper for the bottom LinearLayout is redundant. Remove the AppBarLayout wrapper and make the Behavior class extends CoordinatorLayout.Behavior<LinearLayout>. Finally add the behavior to LinearLayout, then the top Toolbar and bottom LinearLayout enter and exit screen on scroll correctly.
The correct activity xml:
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/top_toolbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:layout_width="fill_parent"
android:orientation="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:id="#+id/recycler_view_container"
android:layout_height="fill_parent"/>
<LinearLayout
android:id="#+id/bottom_bar"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:layout_behavior="*THE_FULL_PACKAGE_NAME*.LinearLayoutBehavior ">
<!-- child views -->
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
The correct Behavior class:
public class LinearLayoutBehavior extends CoordinatorLayout.Behavior<LinearLayout> {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private boolean mIsAnimatingOut = false;
public LinearLayoutBehavior(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final LinearLayout child,
final View directTargetChild, final View target, final int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
#Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final LinearLayout child,
final View target, final int dxConsumed, final int dyConsumed,
final int dxUnconsumed, final int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
if (dyConsumed > 0 && !this.mIsAnimatingOut && child.getVisibility() == View.VISIBLE) {
// User scrolled down and the FAB is currently visible -> hide the FAB
animateOut(child);
} else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) {
// User scrolled up and the FAB is currently not visible -> show the FAB
animateIn(child);
}
}
private void animateOut(final LinearLayout linearLayout) {
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(linearLayout).translationY(168F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer()
.setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
LinearLayoutBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationCancel(View view) {
LinearLayoutBehavior.this.mIsAnimatingOut = false;
}
public void onAnimationEnd(View view) {
LinearLayoutBehavior.this.mIsAnimatingOut = false;
view.setVisibility(View.GONE);
}
}).start();
} else {
Animation anim = AnimationUtils.loadAnimation(linearLayout.getContext(), R.anim.fab_out);
anim.setInterpolator(INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
LinearLayoutBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
LinearLayoutBehavior.this.mIsAnimatingOut = false;
linearLayout.setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(final Animation animation) {
}
});
linearLayout.startAnimation(anim);
}
}
private void animateIn(LinearLayout linearLayout) {
linearLayout.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(linearLayout).translationY(0).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
.start();
} else {
Animation anim = AnimationUtils.loadAnimation(linearLayout.getContext(), R.anim.fab_in);
anim.setDuration(200L);
anim.setInterpolator(INTERPOLATOR);
linearLayout.startAnimation(anim);
}
}
}