How to animate FloatingActionButton like in Google+ app for Android? - android

I set FloatingActionButton to bottom of screen and I want to animate the button.
Hidden when scrolling down
Shown when scrolling up
Like google implemented it in their Google+ app.
I think CoordinatorLayout and AppBarLayout is needed but how to implement it to use it with the FloatingActionButton?

You can achieve it using the default FloatingActionButton changing its
default Behavior using the app:layout_behavior attribute:
You can use a layout like:
<android.support.design.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.support.design.widget.AppBarLayout
android:id="#+id/appbar"
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="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="scroll|enterAlways" />
</android.support.design.widget.AppBarLayout>
// Your layout, for example a RecyclerView
<RecyclerView
.....
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/ic_done"
app:layout_behavior="com.support.android.designlibdemo.ScrollAwareFABBehavior" />
</android.support.design.widget.CoordinatorLayout>
With the app:layout_behavior you can define your own Behavior. With the onStartNestedScroll() and onNestedScroll() methods you can interact with scroll events.
You can use a Behavior like this.
You can find the original code here:
public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private boolean mIsAnimatingOut = false;
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child,
final View directTargetChild, final View target, final int nestedScrollAxes) {
// Ensure we react to vertical scrolling
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
#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 && !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);
}
}
// Same animation that FloatingActionButton.Behavior uses to hide the FAB when the AppBarLayout exits
private void animateOut(final FloatingActionButton button) {
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(button).scaleX(0.0F).scaleY(0.0F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer()
.setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationCancel(View view) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
}
public void onAnimationEnd(View view) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
view.setVisibility(View.GONE);
}
}).start();
} else {
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_out);
anim.setInterpolator(INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
ScrollAwareFABBehavior.this.mIsAnimatingOut = false;
button.setVisibility(View.GONE);
}
#Override
public void onAnimationRepeat(final Animation animation) {
}
});
button.startAnimation(anim);
}
}
// Same animation that FloatingActionButton.Behavior uses to show the FAB when the AppBarLayout enters
private void animateIn(FloatingActionButton button) {
button.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(button).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
.start();
} else {
Animation anim = AnimationUtils.loadAnimation(button.getContext(), R.anim.fab_in);
anim.setDuration(200L);
anim.setInterpolator(INTERPOLATOR);
button.startAnimation(anim);
}
}
}

As of this post, there are no methods that will automatically handle hiding and showing the FloatingActionButton in the Design Support Libraries. I know this because this was my first assignment at work.
The methods you are thinking of are used to animate the FloatingActionButton up and down when a Snackbar is created, and yes, that will work if you are using a CoordinatorLayout.
Here's my code. It's based off of this repo. It has listeners for RecyclerView and AbsListView that handle animating the button automatically. You can either do
button.show();
or
button.hide();
to hide the button manually, or you can call:
button.attachToListView(listView);
and
button.attachToRecyclerView(recyclerView);
and it will hide on scroll down and show on scroll up with no further code.
Hope this helps!
AnimatedFloatingActionButton:
public class AnimatedFloatingActionButton extends FloatingActionButton
{
private static final int TRANSLATE_DURATION_MILLIS = 200;
private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
private boolean mVisible;
public AnimatedFloatingActionButton(Context context, AttributeSet attrs)
{
super(context, attrs);
Log.i("Abscroll", "mVisible" + mVisible);
}
public void show() {
show(true);
}
public void hide() {
hide(true);
}
public void show(boolean animate) {
toggle(true, animate, false);
}
public void hide(boolean animate) {
toggle(false, animate, false);
}
private void toggle(final boolean visible, final boolean animate, boolean force) {
if (mVisible != visible || force) {
mVisible = visible;
int height = getHeight();
if (height == 0 && !force) {
ViewTreeObserver vto = getViewTreeObserver();
if (vto.isAlive()) {
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
ViewTreeObserver currentVto = getViewTreeObserver();
if (currentVto.isAlive()) {
currentVto.removeOnPreDrawListener(this);
}
toggle(visible, animate, true);
return true;
}
});
return;
}
}
int translationY = visible ? 0 : height + getMarginBottom();
Log.i("Abscroll", "transY" + translationY);
if (animate) {
this.animate().setInterpolator(mInterpolator)
.setDuration(TRANSLATE_DURATION_MILLIS)
.translationY(translationY);
} else {
setTranslationY(translationY);
}
}
}
private int getMarginBottom() {
int marginBottom = 0;
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
}
return marginBottom;
}
public void attachToListView(#NonNull AbsListView listView)
{
listView.setOnScrollListener(new AbsListViewScrollDetector() {
#Override
void onScrollUp() {
hide();
}
#Override
void onScrollDown() {
show();
}
#Override
void setScrollThreshold() {
setScrollThreshold(getResources().getDimensionPixelOffset(R.dimen.fab_scroll_threshold));
}
});
}
public void attachToRecyclerView(#NonNull RecyclerView recyclerView) {
recyclerView.addOnScrollListener(new RecyclerViewScrollDetector() {
#Override
void onScrollUp() {
hide();
}
#Override
void onScrollDown() {
show();
}
#Override
void setScrollThreshold() {
setScrollThreshold(getResources().getDimensionPixelOffset(R.dimen.fab_scroll_threshold));
}
});
}
}
AbsListViewScrollDetector:
abstract class AbsListViewScrollDetector implements AbsListView.OnScrollListener {
private int mLastScrollY;
private int mPreviousFirstVisibleItem;
private AbsListView mListView;
private int mScrollThreshold;
abstract void onScrollUp();
abstract void onScrollDown();
abstract void setScrollThreshold();
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(totalItemCount == 0) return;
if (isSameRow(firstVisibleItem)) {
int newScrollY = getTopItemScrollY();
boolean isSignificantDelta = Math.abs(mLastScrollY - newScrollY) > mScrollThreshold;
Log.i("Abscroll", "mLastScrollY " + mLastScrollY);
Log.i("Abscroll", "newScrollY " + newScrollY);
if (isSignificantDelta) {
Log.i("Abscroll", "sig delta");
if (mLastScrollY > newScrollY) {
onScrollUp();
Log.i("Abscroll", "sig delta up");
} else {
onScrollDown();
Log.i("Abscroll", "sig delta down");
}
}
mLastScrollY = newScrollY;
} else {
if (firstVisibleItem > mPreviousFirstVisibleItem) {
onScrollUp();
Log.i("Abscroll", "prev up");
} else {
onScrollDown();
Log.i("Abscroll", "prev down");
}
mLastScrollY = getTopItemScrollY();
mPreviousFirstVisibleItem = firstVisibleItem;
}
}
public void setScrollThreshold(int scrollThreshold) {
mScrollThreshold = scrollThreshold;
Log.i("Abscroll", "LView thresh " + scrollThreshold);
}
public void setListView(#NonNull AbsListView listView) {
mListView = listView;
}
private boolean isSameRow(int firstVisibleItem) {
return firstVisibleItem == mPreviousFirstVisibleItem;
}
private int getTopItemScrollY() {
if (mListView == null || mListView.getChildAt(0) == null) return 0;
View topChild = mListView.getChildAt(0);
return topChild.getTop();
}
}
RecyclerViewScrollDetector:
abstract class RecyclerViewScrollDetector extends RecyclerView.OnScrollListener {
private int mScrollThreshold;
abstract void onScrollUp();
abstract void onScrollDown();
abstract void setScrollThreshold();
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
boolean isSignificantDelta = Math.abs(dy) > mScrollThreshold;
if (isSignificantDelta) {
if (dy > 0) {
onScrollUp();
Log.i("Abscroll", "Rview up");
} else {
onScrollDown();
Log.i("Abscroll", "RView down");
}
}
}
public void setScrollThreshold(int scrollThreshold) {
mScrollThreshold = scrollThreshold;
Log.i("Abscroll", "RView thresh " + scrollThreshold);
}
}

Googles Design Support Library will do that.
Try to implement this code to your layout file:
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_add_black"
android:layout_gravity="bottom|end"
app:elevation="6dp"
app:pressedTranslationZ="12dp"/>
It's important that you add compile 'com.android.support:design:22.2.0' to your build.gradle. If you're using eclipse there is another way to add this library to your project.
Important resources:
Design Support Library (II): Floating Action Button
Android Design Support Library
Google Releasing A FABulous New Design Support Library [Updated]

ExtendedFloatingActionButton enough to achieve your goal, no need to handle any logic code, just extend() and shrink() on scrolling
Full example:
<?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:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="#+id/extendedFloatingButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:text="Reorder List"
android:layout_margin="12dp"
app:icon="#drawable/ic_reorder" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And in your code:
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState:
Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
extendedFloatingButton.extend()
}
super.onScrollStateChanged(recyclerView, newState)
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy > 0 || dy < 0 && extendedFloatingButton.isExtended) {
extendedFloatingButton.shrink()
}
}
})

Related

HideBottomViewOnScrollBehavior not working on recyclerview item expand/collapse

I am trying to hide text view on scroll down and show on scroll up it's working fine if I have an item like 10 or 15 but it's not working the same if I have less item
in recyclerview, I have expanded/collapse functionality so it's not the same sometimes
textview not hiding/visible some times I don't understand I added this line to my view which I want to hide/show on scroll
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
XML
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/lnMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#mipmap/bg"
tools:context=".tab.history.view.HistoryFragment">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/mAppBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<RelativeLayout
android:id="#+id/lnActionBar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="#color/colorPrimary">
<TextView
android:id="#+id/tvtitle"
style="#style/fontMedium"
android:layout_width="wrap_content"
android:layout_height="?android:attr/actionBarSize"
android:layout_centerHorizontal="true"
android:gravity="center_vertical"
android:text="#string/beacon"
android:textColor="#color/white"
android:textSize="#dimen/header_font_size" />
</RelativeLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvBeacon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:paddingBottom="#dimen/_40sdp"
android:scrollbars="none"
android:visibility="visible"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="10"
tools:listitem="#layout/raw_beacon" />
<TextView
android:id="#+id/btnBack"
style="#style/fontBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="#dimen/_10sdp"
android:layout_marginBottom="#dimen/_20sdp"
android:background="#drawable/background_button_yellow_20dp"
android:contentDescription="#string/back_button"
android:drawableStart="#drawable/ic_back"
android:layout_gravity="bottom|center_horizontal"
android:drawablePadding="#dimen/_5sdp"
android:gravity="center"
android:padding="#dimen/_10sdp"
android:text="#string/back_to_search"
android:textColor="#color/white"
android:textSize="#dimen/button_font_size"
android:visibility="#{!isScanning ? View.VISIBLE: View.GONE}"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
this is what I did so far but it's not working every time
Note:- Please not I have a recyclerview item which expands onclick so scroll and textview must behave according to that
Any help would be highly appriciated
i don't know why HideBottomViewOnScrollBehavior is not working for you
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
may be it's beacause you have a expand/collapse functionality since you have only recyclerview only in screen so you can also perform this task by adding Custom ScrollListener
MyRecyclerScroll class
public abstract class MyRecyclerScroll extends RecyclerView.OnScrollListener {
private static final float HIDE_THRESHOLD = 100;
private static final float SHOW_THRESHOLD = 50;
int scrollDist = 0;
private boolean isVisible = true;
// We dont use this method because its action is called per pixel value change
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// Check scrolled distance against the minimum
if (isVisible && scrollDist > HIDE_THRESHOLD) {
// Hide fab & reset scrollDist
hide();
scrollDist = 0;
isVisible = false;
}
// -MINIMUM because scrolling up gives - dy values
else if (!isVisible && scrollDist < -SHOW_THRESHOLD) {
// Show fab & reset scrollDist
show();
scrollDist = 0;
isVisible = true;
}
// Whether we scroll up or down, calculate scroll distance
if ((isVisible && dy > 0) || (!isVisible && dy < 0)) {
scrollDist += dy;
}
}
public abstract void show();
public abstract void hide();
}
Activity/Fragment
binding.rvBeacon.addOnScrollListener(object : MyRecyclerScroll() {
override fun show() {
binding.btnBack.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)).start()
}
override fun hide() {
binding.btnBack.animate().translationY(binding.btnBack.getHeight() + 60f)
.setInterpolator(AccelerateInterpolator(2f)).start()
}
})
you can change animation, delay and margin according to your requirement
for more detail refer to this blog
Note: it will not work if your recyclerview inside scrollview
how about make custom behavior?
for example.
public class QuickReturnFooterBehavior extends CoordinatorLayout.Behavior<View> {
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private static final long ANIMATION_DURATION = 200;
private int dyDirectionSum;
private boolean isShowing;
private boolean isHiding;
private boolean isNeedOption = true;
public boolean isNeedOption() {
return isNeedOption;
}
public void setNeedOption(boolean needOption) {
isNeedOption = needOption;
}
public QuickReturnFooterBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull View child, #NonNull View directTargetChild, #NonNull View target, int axes, int type) {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedPreScroll(#NonNull CoordinatorLayout coordinatorLayout, #NonNull View child, #NonNull View target, int dx, int dy, #NonNull int[] consumed, int type) {
// scroll chhange up and down
if (isNeedOption) {
showView(child);
} else {
if (dy > 0 && dyDirectionSum < 0
|| dy < 0 && dyDirectionSum > 0) {
child.animate().cancel();
dyDirectionSum = 0;
}
dyDirectionSum += dy;
if (dyDirectionSum > child.getHeight()) {
hideView(child);
} else if (dyDirectionSum < -child.getHeight()) {
showView(child);
}
}
}
private void hideView(final View view) {
if (isHiding || view.getVisibility() != View.VISIBLE) {
return;
}
ViewPropertyAnimator animator = view.animate()
.translationY(view.getHeight())
.setInterpolator(INTERPOLATOR)
.setDuration(ANIMATION_DURATION);
animator.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
isHiding = true;
}
#Override
public void onAnimationEnd(Animator animator) {
isHiding = false;
view.setVisibility(View.INVISIBLE);
}
#Override
public void onAnimationCancel(Animator animator) {
// show when cancle
isHiding = false;
showView(view);
}
#Override
public void onAnimationRepeat(Animator animator) {
// no-op
}
});
animator.start();
}
private void showView(final View view) {
if (isShowing || view.getVisibility() == View.VISIBLE) {
return;
}
ViewPropertyAnimator animator = view.animate()
.translationY(0)
.setInterpolator(INTERPOLATOR)
.setDuration(ANIMATION_DURATION);
animator.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
isShowing = true;
view.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animator) {
isShowing = false;
}
#Override
public void onAnimationCancel(Animator animator) {
// show when cancle
isShowing = false;
hideView(view);
}
#Override
public void onAnimationRepeat(Animator animator) {
// no-op
}
});
animator.start();
}
}
UPDATE : for check can scroll in behavior
child.canScrollVertically(1) // "Top of list"
child.canScrollVertically(-1) // "End of list"
**UPDATE : add setter and getter **
private boolean isNeedOption = true;

Hide and show viewgroup when we scroll up and down the recyclerView

I want to hide the view group when we scroll down, and show the view when we scroll up in the recyclerview.
This is my code, in it rvSearchItems is Recyclerview, and rlSearch is the Relative Layout that I want to hide and show:
rvSearchItems.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy >= 0) {
if (rlSearch.getVisibility() != View.GONE)
rlSearch.setVisibility(View.GONE);
} else if(dy<-5) {
if (rlSearch.getVisibility() != View.VISIBLE)
rlSearch.setVisibility(View.VISIBLE);
}
}
});
The main issue here is that it works fine when we're scrolling fast. If we scroll slow it flings multiple times.
I take advantage of coordinateLayout to implement this behavior
instead of LinerLayout I want to show/hide CardView when recyclerView scrolls (You can use any ViewGroup in your case)
Basically this answers is implemented with use of FAB and AppBarLayout Behavior as Iin Lake describe in FAB Behavior
CardViewAwareScrollingViewBehavior :: *
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.CardView;
import android.util.AttributeSet;
import android.view.View;
import java.util.List;
public class CardViewAwareScrollingViewBehavior extends AppBarLayout.ScrollingViewBehavior {
int mAnimState = ANIM_STATE_NONE;
static final int ANIM_STATE_NONE = 0;
static final int ANIM_STATE_HIDING = 1;
static final int ANIM_STATE_SHOWING = 2;
static final int SHOW_HIDE_ANIM_DURATION = 200;
public CardViewAwareScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency) ||
dependency instanceof CardView;
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final View child,
final View directTargetChild, final View target, final int nestedScrollAxes) {
// Ensure we react to vertical scrolling
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL
|| super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
#Override
public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final View 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) {
// User scrolled down -> hide the CardView
List<View> dependencies = coordinatorLayout.getDependencies(child);
for (View view : dependencies) {
if (view instanceof CardView) {
hide( ((CardView) view));
}
}
} else if (dyConsumed < 0) {
// User scrolled up -> show the CardView
List<View> dependencies = coordinatorLayout.getDependencies(child);
for (View view : dependencies) {
if (view instanceof CardView) {
show((CardView) view);
}
}
}
}
void show(final View mView) {
if (isOrWillBeShown(mView)) {
// We either are or will soon be visible, skip the call
return;
}
mView.animate().cancel();
if (shouldAnimateVisibilityChange(mView)) {
mAnimState = ANIM_STATE_SHOWING;
if (mView.getVisibility() != View.VISIBLE) {
// If the view isn't visible currently, we'll animate it from a single pixel
mView.setAlpha(0f);
mView.setScaleY(0f);
mView.setScaleX(0f);
}
mView.animate()
.scaleX(1f)
.scaleY(1f)
.alpha(1f)
.setDuration(SHOW_HIDE_ANIM_DURATION)
// .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
// mView.internalSetVisibility(View.VISIBLE, false);
mView.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animation) {
mAnimState = ANIM_STATE_NONE;
}
});
} else {
// mView.internalSetVisibility(View.VISIBLE, fromUser);
mView.setVisibility(View.VISIBLE);
mView.setAlpha(1f);
mView.setScaleY(1f);
mView.setScaleX(1f);
}
}
void hide(final View mView) {
if (isOrWillBeHidden(mView)) {
// We either are or will soon be hidden, skip the call
return;
}
mView.animate().cancel();
if (shouldAnimateVisibilityChange(mView)) {
mAnimState = ANIM_STATE_HIDING;
mView.animate()
.scaleX(0f)
.scaleY(0f)
.alpha(0f)
.setDuration(SHOW_HIDE_ANIM_DURATION)
// .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR)
.setListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
#Override
public void onAnimationStart(Animator animation) {
// mView.internalSetVisibility(View.VISIBLE, fromUser);
mView.setVisibility(View.VISIBLE);
mCancelled = false;
}
#Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
#Override
public void onAnimationEnd(Animator animation) {
mAnimState = ANIM_STATE_NONE;
if (!mCancelled) {
// mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE,
// fromUser);
mView.setVisibility(View.GONE);
// if (listener != null) {
// listener.onHidden();
// }
}
}
});
} else {
// If the view isn't laid out, or we're in the editor, don't run the animation
// mView.internalSetVisibility(true ? View.GONE : View.INVISIBLE, fromUser);
mView.setOverScrollMode(View.GONE);
// if (listener != null) {
// listener.onHidden();
// }
}
}
boolean isOrWillBeShown(View mView) {
if (mView.getVisibility() != View.VISIBLE) {
// If we not currently visible, return true if we're animating to be shown
return mAnimState == ANIM_STATE_SHOWING;
} else {
// Otherwise if we're visible, return true if we're not animating to be hidden
return mAnimState != ANIM_STATE_HIDING;
}
}
private boolean shouldAnimateVisibilityChange(View mView) {
return ViewCompat.isLaidOut(mView) && !mView.isInEditMode();
}
boolean isOrWillBeHidden(View mView) {
if (mView.getVisibility() == View.VISIBLE) {
// If we currently visible, return true if we're animating to be hidden
return mAnimState == ANIM_STATE_HIDING;
} else {
// Otherwise if we're not visible, return true if we're not animating to be shown
return mAnimState != ANIM_STATE_SHOWING;
}
}
}
CustomerCardView that use CardViewAwareScrollingViewBehavior :: *
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.CardView;
import android.util.AttributeSet;
#CoordinatorLayout.DefaultBehavior(CardViewAwareScrollingViewBehavior.class)
public class CustomerCardView extends CardView {
public CustomerCardView(Context context) {
super(context);
}
public CustomerCardView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomerCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
and use this customCardView in xml like below
main.xml
<?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"
tools:context=".MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titleTextColor="#android:color/white">
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main"/>
</android.support.design.widget.CoordinatorLayout>
and content_main.xml which is included in main.xml
<?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"
app:layout_behavior="prathap.recyclerviewsample.adapters.UI.CardViewAwareScrollingViewBehavior"
tools:context="prathap.recyclerviewsample.MainActivity"
tools:showIn="#layout/acitivity_main">
<!--this is out custom Cardview with custom behaviour -->
<prathap.recyclerviewsample.CustomViews.CustomerCardView
android:id="#+id/card_"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#mipmap/ic_launcher"/>
</prathap.recyclerviewsample.CustomViews.CustomerCardView>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="prathap.recyclerviewsample.adapters.UI.CardViewAwareScrollingViewBehavior"
/>
</android.support.design.widget.CoordinatorLayout>
With this we got CardView hide when recyclerView scroll up and show when recycleView scroll down.
here is my sample screencast
Here is my code to extend the RecyclerView class for Kotlin. Just add it to your project and use in Fragment or Activity as this:
myRecyclerView.addOnScrollHiddenView(myAnyHiddenView, resources.dpToPx(myValuedp))
.........
fun RecyclerView.addOnScrollHiddenView(
hiddenView: View,
translationX: Float = 0F,
translationY: Float = 0F,
duration: Long = 200L
) {
var isViewShown = true
this.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
when{
dy> 0 && isViewShown -> {
isViewShown = false
hiddenView.animate()
.translationX(translationX)
.translationY(translationY)
.duration = duration
}
dy < 0 && !isViewShown ->{
isViewShown = true
hiddenView.animate()
.translationX(0f)
.translationY(0f)
.duration = duration
}
}
}
})
}

Scroll aware FAB hides, but then does not reappear

I have made an Android app with a recyclerview and a floating action button. When scrolling down, the button should hide, when scrolling up it should show again. I have used this tutorial to implement the behavior.
The result is, that the FAB hides when I scroll down, but when scrolling up it does not reappear again :( The class ScrollAwareFABBehavior is identical from the tutorial. But I'm using nested layouts.
Here is my layout (the recyclerview is in a LinearLayout in content_main):
<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="org.myorganisation.mypackage.someActivity">
<include layout="#layout/toolbar" />
<include layout="#layout/content_main" />
<android.support.design.widget.FloatingActionButton
android:id="#+id/add_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
android:src="#drawable/plus"
app:layout_behavior="org.myorganisation.mypackage.someActivity.helpers.ScrollAwareFABBehavior" />
<LinearLayout
android:id="#+id/insert_alert"
android:layout_width="wrap_content"
android:layout_height="50sp"
android:layout_margin="#dimen/fab_margin"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingEnd="70sp"
android:visibility="gone"
app:layout_anchor="#id/add_fab"
app:layout_anchorGravity="bottom|left">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="#string/initial_alert"
android:textColor="#color/colorPrimaryDark"
android:textStyle="bold|italic" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_keyboard_arrow_right_black_24sp"
android:tint="#color/colorPrimary" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
As of version 25.1.0 of the Support Library, hidden views no longer scroll events as per this bug.
As mentioned in comment #5:
This is working as intended, your dependency chain is the wrong way. Your RecyclerView (Behavior) should be triggering the FAB visibility change.
As for support library version 25.1.0, the solution is here.
Here is simple solution:
The concept behind this is very simple, you just need to detect when user scrolls down and up the RecyclerView. In Android there are some built in methods that can help us detect when user is scrolling either side in Recyclerview. See the code below, since it will show the full concept:
RecyclerView rv = (RecyclerView)findViewById(R.id.rv);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dy < 0) {
fab.show();
} else if (dy > 0) {
fab.hide();
}
}
});
I solved this problem by replacing
.setVisibility(View.VISIBLE) with .show(true) and
.setVisibility(View.GONE) with .hide(true):
And here is my class for the Behaviour, that works with 25.1.0: and Clans FloatingActionButton
public class ScrollAwareFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private static boolean mIsAnimatingOut = false;
private static final Interpolator INTERPOLATOR = new FastOutSlowInInterpolator();
public ScrollAwareFABBehavior(Context context, AttributeSet attrs) {
super();
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fabButton, View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fabButton, View dependency) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight());
fabButton.setTranslationY(translationY);
return true;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
FloatingActionButton fabButton, View directTargetChild, View target, int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL ||
super.onStartNestedScroll(coordinatorLayout, fabButton, directTargetChild, target,
nestedScrollAxes);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton fabButton,
View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, fabButton, target, dxConsumed, dyConsumed, dxUnconsumed,
dyUnconsumed);
if (fabButton.isEnabled()) {
if (dyConsumed > 0 && !mIsAnimatingOut && fabButton.isShown()) {
animateOut(fabButton);
} else if ((dyConsumed < 0 || dyUnconsumed < 0) && fabButton.isHidden()) {
animateIn(fabButton);
}
}
}
public static void animateOut(final FloatingActionButton fabButton) {
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(fabButton).translationY(168F).alpha(0.0F).setInterpolator(INTERPOLATOR).withLayer()
.setListener(new ViewPropertyAnimatorListener() {
public void onAnimationStart(View view) {
mIsAnimatingOut = true;
}
public void onAnimationCancel(View view) {
mIsAnimatingOut = false;
}
public void onAnimationEnd(View view) {
mIsAnimatingOut = false;
fabButton.hide(true);
}
}).start();
} else {
Animation anim = AnimationUtils.loadAnimation(fabButton.getContext(), android.R.anim.fade_in);
anim.setInterpolator(INTERPOLATOR);
anim.setDuration(200L);
anim.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation animation) {
mIsAnimatingOut = true;
}
public void onAnimationEnd(Animation animation) {
mIsAnimatingOut = false;
fabButton.hide(true);
}
#Override
public void onAnimationRepeat(final Animation animation) {
}
});
fabButton.startAnimation(anim);
}
}
public static void animateIn(FloatingActionButton fabButton) {
fabButton.show(true);
if (Build.VERSION.SDK_INT >= 14) {
ViewCompat.animate(fabButton).translationY(0).scaleX(1.0F).scaleY(1.0F).alpha(1.0F)
.setInterpolator(INTERPOLATOR).withLayer().setListener(null)
.start();
} else {
Animation anim = AnimationUtils.loadAnimation(fabButton.getContext(), android.R.anim.fade_out);
anim.setDuration(200L);
anim.setInterpolator(INTERPOLATOR);
fabButton.startAnimation(anim);
}
}
}

How to animate both top and bottom Toolbars(or any other view) enter/exit screen on scroll in CoordinatorLayout?

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);
}
}
}

CoordinatorLayout: Hiding/Showing half visible toolbar?

Id like to achieve a similar effect as the one you can see in Google Play store, where by scrolling the content the Toolbar goes off-screen as you scroll.
This works fine with the CoordinatorLayout (1) introduced at #io15, however: If you stop the scroll "mid-way" the Toolbar remains on screen, but is cut in half: I want it to animate off-screen, just like in the Google Play store. How can I achieve that?
Now the Android Support Library 23.1.0 has a new scroll flag SCROLL_FLAG_SNAP which allows you to achieve this effect.
AppBarLayout supports a number of scroll flags which affect how children views react to scrolling (e.g. scrolling off the screen). New to this release is SCROLL_FLAG_SNAP, ensuring that when scrolling ends, the view is not left partially visible. Instead, it will be scrolled to its nearest edge, making fully visible or scrolled completely off the screen.
Activity Layout file :
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize"
android:clipToPadding="false"/>
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
</FrameLayout>
Now inside the activity, setup Toolbar and RecyclerView. Assign OnScrollListener to RecyclerView
recyclerView.setOnScrollListener(new MyScrollListener(this));
Extend MyScrollListerner from RecyclerView.OnScrollListener.
public abstract class MyScrollListener extends RecyclerView.OnScrollListener {
private static final float TOOLBAR_HIDE_THRESHOLD = 10;
private static final float TOOLBAR_SHOW_THRESHOLD = 70;
private int mToolbarOffset = 0;
private boolean mControlsVisible = true;
private int mToolbarHeight;
private int mTotalScrolledDistance;
public MyScrollListener(Context context) {
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.actionBarSize});
mToolbarHeight = (int) styledAttributes.getDimension(0, 0);
styledAttributes.recycle();
return toolbarHeight;
mToolbarHeight = Utils.getToolbarHeight(context);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
if(mTotalScrolledDistance < mToolbarHeight) {
setVisible();
} else {
if (mControlsVisible) {
if (mToolbarOffset > TOOLBAR_HIDE_THRESHOLD) {
setInvisible();
} else {
setVisible();
}
} else {
if ((mToolbarHeight - mToolbarOffset) > TOOLBAR_SHOW_THRESHOLD) {
setVisible();
} else {
setInvisible();
}
}
}
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
clipToolbarOffset();
onMoved(mToolbarOffset);
if((mToolbarOffset <mToolbarHeight && dy>0) || (mToolbarOffset >0 && dy<0)) {
mToolbarOffset += dy;
}
if (mTotalScrolledDistance < 0) {
mTotalScrolledDistance = 0;
} else {
mTotalScrolledDistance += dy;
}
}
private void clipToolbarOffset() {
if(mToolbarOffset > mToolbarHeight) {
mToolbarOffset = mToolbarHeight;
} else if(mToolbarOffset < 0) {
mToolbarOffset = 0;
}
}
private void setVisible() {
if(mToolbarOffset > 0) {
onShow();
mToolbarOffset = 0;
}
mControlsVisible = true;
}
private void setInvisible() {
if(mToolbarOffset < mToolbarHeight) {
onHide();
mToolbarOffset = mToolbarHeight;
}
mControlsVisible = false;
}
public abstract void onMoved(int distance);
public abstract void onShow();
public abstract void onHide();
}
Overriding the AppBarLayout seems to be a better solution, as there are two possible scroll-events - of the entire CoordinatorLayout, and of the RecyclerView/NestedScrollView
See this answer as a possible working code:
https://stackoverflow.com/a/32110089/819355

Categories

Resources