Hide FAB in NestedScrollView when scrolling - android

I am having a nestedscrollview with content like some linearlayouts and textviews.
I am using a floatingactionbutton library for some reasons, as well. So I can't use any behavior for it.
I don't know how I should handle the scrollchangelistener from scrollview to hide and show the fab dynamically like a behavior.
Any suggestions how to hide and show the fab while scrolling?

Simple add this code below to your NestedScrollView ScrollChangeListener:
NestedScrollView nsv = v.findViewById(R.id.nsv);
nsv.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
#Override
public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (scrollY > oldScrollY) {
fab.hide();
} else {
fab.show();
}
}
});

Create FabScrollBehavior class
public class FabScrollBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private int toolbarHeight;
public FabScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.toolbarHeight = AppUtil.getToolbarHeight(context);
}
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
return dependency instanceof AppBarLayout;
}
#Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
if (dependency instanceof AppBarLayout) {
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
int distanceToScroll = fab.getHeight() + fabBottomMargin;
float ratio = (float)dependency.getY()/(float)toolbarHeight;
fab.setTranslationY(-distanceToScroll * ratio);
}
return true;
}
}
Where AppUtil.getToolbarHeight(context) is -
public static int getToolbarHeight(Context context) {
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
new int[]{R.attr.actionBarSize});
int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
styledAttributes.recycle();
return toolbarHeight;
}
then in your layout add to FloatingActionButton layout_behavior:
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_task_accept"
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_accepted"
app:layout_behavior="pass.to.your.FabScrollBehavior.Class"
app:theme="#style/Widget.AppTheme.Fab"/>
The whole layout looks like
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/Widget.AppTheme.AppBarOverlay">
<include
layout="#layout/include_layout_toolbar_scroll"/>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/include_layout_content_with_nestedscroll"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab_task_accept"
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_accepted"
app:layout_behavior="pass.to.FabScrollBehavior.Class"
app:theme="#style/Widget.AppTheme.Fab"/>
</android.support.design.widget.CoordinatorLayout>
Implemented from https://mzgreen.github.io/2015/02/15/How-to-hideshow-Toolbar-when-list-is-scroling(part1)/

define variable type int in your Activity or fragment to set previous Scroll from ScrollView then use this method to listen change scroll in ScrollView Class
scrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
// previousScrollY this variable is define in your Activity or Fragment
if (scrollView.getScrollY() > previousScrollY && floatingActionButton.getVisibility() == View.VISIBLE) {
floatingActionButton.hide();
} else if (scrollView.getScrollY() < previousScrollY && floatingActionButton.getVisibility() != View.VISIBLE) {
floatingActionButton.show();
}
previousScrollY = scrollView.getScrollY();
}
});
will work done all version of android

After spending such time i have found the solution for it.
It may work in all situations. Though it is a hack not the proper solution but you can apply it to make this thing work.
As we know setOnScrollChangeListener will only work if minimum api 23, so what if my minimum api level is less then 23.
So I found out solution from stack overflow that we can use getViewTreeObserver().addOnScrollChangedListener for that so this will be compatible solution for all devices.
Now let's move to the final solution of over problem "Hide fab button when nested scroll view scrolling and Show fab button when nested scroll view in ideal state"
So for that we can use Handler with postDelayed to slove this issue.
Define on variable in you context private int previousScrollY = 0;
Then use getViewTreeObserver().addOnScrollChangedListener to your nested scroll view like this.
NESTEDSCROLLVIEW.getViewTreeObserver().addOnScrollChangedListener(new
ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
if (NESTEDSCROLLVIEW.getScrollY() == previousScrollY) {
FABBUTTON.setVisibility(View.VISIBLE);
} else {
FABBUTTON.setVisibility(View.INVISIBLE);
}
}
}, 10);
previousScrollY = NESTEDSCROLLVIEW.getScrollY();
}
});
Now you are ready to go....

You can use this listener to observe and hide FAB when scrolling.
nestedScrollView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
#Override
public void onScrollChanged() {
if (nestedScrollView != null) {
if (nestedScrollView.getChildAt(0).getBottom() <= (nestedScrollView.getHeight() + nestedScrollView.getScrollY())) {
fab.setVisibility(View.INVISIBLE);
} else {
fab.setVisibility(View.VISIBLE);
}
}
}
});

Related

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 hide or show the partially hidden toolbar/actionbar when you stop scrolling the listview/recyclerview in android

So to show and hide the action bar when scrolling I am using this method
using coordinator layout and appbarlayout
But when I stop scrolling the list in the middle of toolbar being hidden or shown it stays there and only part of toolbar is visible.
What I want to do is make the toolbar show or hide completely based on the percentage of toolbar visible.
Is there a way I can acheive this using the coordinator layout and appbarlayout?
EDIT - Just get 23.1.0 design library and add "|snap" attribute to your ToolBar layout:
<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 />
-----
-----
SO YOU DON'T NEED TO USE THE FOLLOWING CODE:
Have you tried to add the layout_behaviour attribute to AppBarLayout?
app:layout_behavior="AppBarLayoutSnapBehavior"
Then you need to create class "AppBarLayoutSnapBehavior":
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();
}
Should work for you.
I've got partial solution. To your recyclerview add onScrollListener bellow, with AppBarLayout in param
public class ImprovedScrollListener extends RecyclerView.OnScrollListener {
private WeakReference<AppBarLayout> mAppBarLayout;
public ImprovedScrollListener(AppBarLayout appBarLayout) {
mAppBarLayout = new WeakReference<>(appBarLayout);
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && mAppBarLayout.get() != null) {
if (!recyclerView.canScrollVertically(-1) && !recyclerView.canScrollVertically(1)) {
/// TODO: 21.10.2015 handle this state
}else if(!recyclerView.canScrollVertically(-1)){
mAppBarLayout.get().setExpanded(true, true);
} else {
mAppBarLayout.get().setExpanded(false, true);
}
}
}
}
This will work for recyclerviews that can scroll, this still behaves ugly when recyclerview is not scrollable (see TODO in code). If somebody find solution for this case let me know
Setting Toolbar within AppBarLayout and cordinationLayout will itself take care of hiding and showing the actionBar
<?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"
android:layout_width="match_parent"
android:id="#+id/cordinationLayout"
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="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

How to animate FloatingActionButton like in Google+ app for 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()
}
}
})

Need to disable expand on CollapsingToolbarLayout for certain fragments

I have an AppCompatActivity that controls replacing many fragments. Here is my layout for it.
activity_main.xml
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true">
<include layout="#layout/activity_main_frame"/>
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:background="#color/white"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/drawer"/>
</android.support.v4.widget.DrawerLayout>
activity_main_frame.xml
<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:fitsSystemWindows="true">
<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: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|exitUntilCollapsed"
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="256dp"
android:scaleType="centerCrop"
android:fitsSystemWindows="true"
app:layout_collapseMode="parallax" />
<include layout="#layout/activity_main_items"/>
<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.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="bottom"/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" >
</FrameLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
app:layout_anchor="#id/appbar"
app:layout_anchorGravity="bottom|right|end"
app:borderWidth="0dp"
android:src="#drawable/app_ic_slide_wallpaper_dark"
android:layout_margin="#dimen/big_padding"
android:clickable="true"/>
</android.support.design.widget.CoordinatorLayout>
My home fragment is set initially and that is where I want the collapsing toolbar expanded and that works fine. However when I change fragments from side drawer I want to disable the expanding toolbar.
I have figured out how to collapse it when a drawer item is selected but I also need to make sure it doesn't expand unless the home fragment is displayed. is this possible?
public void collapseToolbar(){
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appbar.getLayoutParams();
behavior = (AppBarLayout.Behavior) params.getBehavior();
if(behavior!=null) {
behavior.onNestedFling(coordinator, appbar, null, 0, 10000, true);
}
}
Disable nested scrolling on the scrolling fragment content:
recyclerView.setNestedScrollingEnabled(false);
Use this if you're using the support library:
ViewCompat.setNestedScrollingEnabled(recyclerView, false);
This class will let you disable/re-enable the expansion behavior.
public class DisableableAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean mEnabled;
public DisableableAppBarLayoutBehavior() {
super();
}
public DisableableAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) {
return mEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes);
}
public boolean isEnabled() {
return mEnabled;
}
}
Use it in your layout like so:
<android.support.design.widget.AppBarLayout
... other attributes ...
app:layout_behavior="com.yourpackage.DisableableAppBarLayoutBehavior"
>
<!-- your app bar contents -->
</android.support.design.widget.AppBarLayout>
Then, when you want to disable the behavior:
AppBarLayout myAppBar = ....;
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) myAppBar.getLayoutParams();
((DisableableAppBarLayoutBehavior) layoutParams.getBehavior()).setEnabled(false);
Now, in v23 of support library you can easily control your appbar visibility.
Just get a reference to your AppBarLayout and hide/show it depending on the fragment you want to load:
private AppBarLayout appBarLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
[...]
appBarLayout = (AppBarLayout) findViewById(R.id.appbar);
[...]
}
public void switchToFragment(Fragment fragment, String tag, boolean expandToolbar){
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment currentFragment = fragmentManager.findFragmentByTag(currentFragmentTag);
if(currentFragment == null || !TextUtils.equals(tag, currentFragmentTag) ){
currentFragmentTag = tag;
fragmentManager
.beginTransaction()
.replace(R.id.flContent, fragment, currentFragmentTag)
.commit();
if(expandToolbar){
appBarLayout.setExpanded(true,true);
}else{
appBarLayout.setExpanded(false,true);
}
}
}
P.S. don't forget to add the required dependencies in your build.gradle:
dependencies {
compile 'com.android.support:design:23.2.1'
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:recyclerview-v7:23.2.1'
}
EDIT:
If you also want to lock your toolbar in certain fragments (apart from collapsing) you have to resort to workarounds as this feature is not provided by CollapsingToolbarLayout until now (v23.2.1 of support design). Here you can find my proposed workaround.
All you have to do is replace CoordinatorLayout with custom implementation of CoordinatorLayout which will cheat that nested scrolling has been handled.
MyCoordinatorLayout implementation:
public class MyCoordinatorLayout extends CoordinatorLayout {
private boolean allowForScroll = false;
public MyCoordinatorLayout(Context context) {
super(context);
}
public MyCoordinatorLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return allowForScroll && super.onStartNestedScroll(child, target, nestedScrollAxes);
}
public boolean isAllowForScroll() {
return allowForScroll;
}
public void setAllowForScroll(boolean allowForScroll) {
this.allowForScroll = allowForScroll;
}
}
activity view xml:
<LinearLayout
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:orientation="vertical"
>
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!--CONTENT-->
<com.example.views.MyCoordinatorLayout
android:id="#+id/coordinator"
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"
>
<com.example.views.ControllableAppBarLayout
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="?attr/colorPrimary"
app:expandedTitleMarginBottom="32dp"
app:expandedTitleMarginEnd="64dp"
app:expandedTitleMarginStart="48dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:id="#+id/header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/primary"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax" />
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light" />
</android.support.design.widget.CollapsingToolbarLayout>
</com.example.views.ControllableAppBarLayout>
<FrameLayout
android:id="#+id/flContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
/>
</com.example.views.MyCoordinatorLayout>
<!-- DRAWER -->
<fragment
android:id="#+id/fDrawer"
android:name="com.example.fragment.DrawerFragment"
android:layout_width="#dimen/drawer_width"
android:layout_height="match_parent"
android:layout_gravity="left|start"
android:fitsSystemWindows="true"
android:clickable="true"
/>
</android.support.v4.widget.DrawerLayout>
</LinearLayout>
I encourage you to use custom AppBarLayout implementation with helper methods to collapse/expand toolbar. On this gist you can find one.
Ok, now it's time to configure our toolbar in activity.
public class ToolbarAppcompatActivity extends AppCompatActivity
implements AppBarLayout.OnOffsetChangedListener {
protected Toolbar toolbar;
protected MyCoordinatorLayout coordinator;
protected ControllableAppBarLayout appbar;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
configureToolbar();
switchFragment(new FooFragment(), "FOO", true);
}
protected void configureToolbar() {
toolbar = (Toolbar) findViewById(R.id.toolbar);
coordinator = (MyCoordinatorLayout) findViewById(R.id.coordinator);
appbar = (ControllableAppBarLayout) findViewById(R.id.appbar);
appbar.addOnOffsetChangedListener(this);
getDelegate().setSupportActionBar(toolbar);
}
public void switchToFragment(Fragment fragment, String tag, boolean expandToolbar){
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment currentFragment = fragmentManager.findFragmentByTag(currentFragmentTag);
if(currentFragment == null || !TextUtils.equals(tag, currentFragmentTag) ){
currentFragmentTag = tag;
fragmentManager
.beginTransaction()
.replace(R.id.flContent, fragment, currentFragmentTag)
.commit();
if(expandToolbar){
expandToolbar();
}else{
collapseToolbar();
}
}
}
protected void addFragment(Fragment fragment, String tag, boolean expandToolbar) {
FragmentManager fragmentManager = getSupportFragmentManager();
currentFragmentTag = tag;
fragmentManager
.beginTransaction()
.add(R.id.flContent, fragment, currentFragmentTag)
.addToBackStack(tag)
.commit();
if(expandToolbar){
expandToolbar();
}else{
collapseToolbar();
}
}
protected void collapseToolbar(){
appbar.collapseToolbar();
coordinator.setAllowForScroll(false);
}
public void expandToolbar(){
appbar.expandToolbar();
coordinator.setAllowForScroll(true);
}
}
Every time you want to switch fragment and collapse/expand toolbar just
call method switchFragment/addFragment with proper boolean parameter.
Just one last note. Make sure you use latest support libraries.
dependencies {
// android support
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.android.support:recyclerview-v7:22.2.1'
compile 'com.android.support:design:22.2.1'
}
Do not use include tag in AppBarLayout. It does not work
The following code achieves 3 objectives:
Disable CollapsingToolbarLayout expand or collapse by the user, but still allow AppBarLayout.setExpanded.
Prevent scrolling of RecyclerView or NestedScrollView from expanding or collapsing the CollapsingToolbarLayout.
// scrollView can be RecyclerView or NestedScrollView
ViewCompat.setNestedScrollingEnabled(scrollView, false)
Prevent the user from expanding or collapsing the CollapsingToolbarLayout by flicking the AppBar.
val params = appBar.layoutParams as CoordinatorLayout.LayoutParams
if (params.behavior == null)
params.behavior = AppBarLayout.Behavior()
val behaviour = params.behavior as AppBarLayout.Behavior
behaviour.setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
override fun canDrag(appBarLayout: AppBarLayout): Boolean {
return false
}
})
https://code.luasoftware.com/tutorials/android/how-to-disable-or-lock-collapsingtoolbarlayout-collapse-or-expand/
With Android Design Library v23.1.1, the method described by #LucyFair does not work. I managed to get it to work by setting the app:layout_scrollFlags to enterAlwaysCollapsed only, and the appbar stays "locked".
Hope this helps. :)
I have used #JasonWyatt's solution and added DragCallback to behavior class to prevent touch and drag CollapsingToolbarLayout to expand it
private void setDragCallback() {
setDragCallback(new DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return mEnabled;
}
});
}
I have found a workaround solution that works with activity and various fragments. You implement the CollapsingToolbarLayout with AppBar etc in your activity and then each time you call a new fragment you can call these 2 functions.
When I want my appbar to keep collapsed:
public void lockAppBarClosed() {
mAppBarLayout.setExpanded(false, false);
mAppBarLayout.setActivated(false);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_height);
}
When I want my appbar to be expanded and scrollable again
public void unlockAppBarOpen() {
mAppBarLayout.setExpanded(true, false);
mAppBarLayout.setActivated(true);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_expand_height);
}
You can call thoses functions from your fragments by implementing an interface. Here's a quick example, for your case (toolbar expands only in homeFragment)
public interface CustomListener() {
void unlockAppBarOpen();
void lockAppBarClosed()
}
public class MainActivity extends BaseActivity implements CustomListener {
#Override
public void unlockAppBarOpen() {
mAppBarLayout.setExpanded(true, false);
mAppBarLayout.setActivated(true);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_expand_height);
}
#Override
public void lockAppBarClosed() {
mAppBarLayout.setExpanded(false, false);
mAppBarLayout.setActivated(false);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)mAppBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbar_height);
}
}
public class MainFragment extends BaseFragment {
#Override
public void onResume() {
super.onPause();
((MainActivity) getContext()).unlockAppBarOpen();
}
}
public class SecondFragment extends BaseFragment {
#Override
public void onResume() {
super.onPause();
((MainActivity) getContext()).lockAppBarClosed();
}
}
With this example:
each time the MainFragment will be displayed -> it will extends the toolbar and make it collapsable and expandable
each time the SecondFragment is displayed -> il will collapsed the toolbar to a standard size and prevent it to expand again
Hope it will help you !
I found Simple solution to enable/disable collapse in CollapsingToolbarLayout:
private void setExpandEnabled(boolean enabled) {
mAppBarLayout.setExpanded(enabled, false);
mAppBarLayout.setActivated(enabled);
final AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
if (enabled)
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
else
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
collapsingToolbarLayout.setLayoutParams(params);
}
I can't comment, so I will post my additions to JasonWyatt's DisableableAppBarLayoutBehavior solution as independent answer.
public class DisableableAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean mEnabled = true; // enabled by default
public DisableableAppBarLayoutBehavior() {
super();
}
public DisableableAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
return mEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
#Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
if (!isEnabled()) return;
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
}
#Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
if (!isEnabled()) return;
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
public boolean isEnabled() {
return mEnabled;
}
}
In addition to onStartNestedScroll also lock onNestedPreScroll and onNestedScroll itself to avoid unexpected behaviour. For example, in my case - calling setExpanded(false, true) on my app bar was braking expected behaviour and still was expanding with lags. Now it works:
LayoutParams layoutParams = (LayoutParams) context.appBarLayout.getLayoutParams();
((DisableableAppBarLayoutBehavior)layoutParams.getBehavior()).setEnabled(false);
context.appBarLayout.setLayoutParams(layoutParams);
context.appBarLayout.setExpanded(false, true); // collapse app bar
None of the provided solutions worked for me except this one. With this solution, i can easily manage the state of collapsing toolbar. This will prevent expanding of collapsing toolbar and set title for it.
public void lockAppBar(boolean locked,String title) {
if(locked){
appBarLayout.setExpanded(false, true);
int px = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, getResources().getDisplayMetrics());
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)appBarLayout.getLayoutParams();
lp.height = px;
appBarLayout.setLayoutParams(lp);
collapsingToolbarLayout.setTitleEnabled(false);
toolbar.setTitle(title);
}else{
appBarLayout.setExpanded(true, false);
appBarLayout.setActivated(true);
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
lp.height = (int) getResources().getDimension(R.dimen.toolbarExpandHeight);
collapsingToolbarLayout.setTitleEnabled(true);
collapsingToolbarLayout.setTitle(title);
}
}
Find the AppBarLayout id as like this.
appBarLayout = (AppBarLayout) findViewById(R.id.appbar);
Disable expand on CollapsingToolbarLayout for certain fragments
appBarLayout.setExpanded(true,true);
Enable expand on CollapsingToolbarLayout for certain fragments
appBarLayout.setExpanded(false,true);
Hope it will help you !!
Locking and unlocking was not enough, simple lockings keeps the image shrinked; here my solution
Call this on resume that requires toolbar in collapsed mode.
private void lockAppBarClosed() {
appBarLayout.setExpanded(false,true);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(false);
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
}
This is for a fragment that requires Snap
public void unlockAppBarOpen() {
appBarLayout.setExpanded(true,true);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(true);
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
}
This is for a fragment requires scroll with SCROLL_FLAG_EXIT_UNTIL_COLLAPSED mode.
public void unlockAppBarOpen() {
appBarLayout.setExpanded(true,true);
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
((CustomAppBarLayoutBehavior)layoutParams.getBehavior()).setScrollBehavior(true);
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) collapsingToolbarLayout.getLayoutParams();
params.setScrollFlags(0);
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout.LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED);
}
And the CustomAppBarLayoutBehavior.java
public class CustomAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean shouldScroll = false;
public CustomAppBarLayoutBehavior() {
super();
}
public CustomAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes, int type) {
return shouldScroll;
}
#Override
public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
if(shouldScroll){
return super.onTouchEvent(parent, child, ev);
}else{
return false;
}
}
public void setScrollBehavior(boolean shouldScroll){
this.shouldScroll = shouldScroll;
}
public boolean isShouldScroll(){
return shouldScroll;
}
}
provide this behavior to AppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="210dp"
app:layout_behavior=".behaviors.CustomAppBarLayoutBehavior"
android:theme="#style/ThemeOverlay.MaterialComponents.ActionBar.Primary">
Thanks to #Desmond Lua's great answer I was finally able to fix this problem in my code. Here is my adapted solution using Java and Data Binding.
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar"
bind:expanded="#{myCondition? false : true}"
app:disableCollapsingScroll="#{myCondition? false : true}"
... >
<com.google.android.material.appbar.CollapsingToolbarLayout
... >
...
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:nestedScrollingEnabled="#{myCondition? false : true}"
... >
...
</NestedScrollView>
#BindingAdapter("disableCollapsingScroll")
public static void bindDisableCollapsingScroll(AppBarLayout appBarLayout, boolean disabled) {
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
if (params.getBehavior() == null) {
params.setBehavior(new AppBarLayout.Behavior());
}
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return disabled;
}
});
}
You can lock appbarlayout expansion by resetting collapsing toolbar height to toolbar height
toolbarHeight = toolbar.getLayoutParams().height;
if (expand) {
collapsingToolbar.getLayoutParams().height = getResources().getDimensionPixelOffset(R.dimen.collapsingToolbarDefaultHeight);
appBarLayout.setExpanded(true, true);
} else {
//collapse
//** it is important you do this before resetting **
appBarLayout.setExpanded(false, true);
appBarLayout.postDelayed(new Runnable() {
#Override
public void run() {
collapsingToolbar.getLayoutParams().height = toolbarHeight;
}
}, 700/* 600 is default animation time to collapse */);
}
The answer of #JasonWyatt works when dragging the scrolling view; but when dragging the appBarLayout it doesn't.
To fix this register a BaseDragCallback listener in the constructors of the custom class to return the mEnabled boolean in the callback:
public class DisableableAppBarLayoutBehavior extends AppBarLayout.Behavior {
private boolean mEnabled;
public DisableableAppBarLayoutBehavior() {
super();
super.setDragCallback(new BaseDragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return mEnabled;
}
});
}
public DisableableAppBarLayoutBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
super.setDragCallback(new BaseDragCallback() {
#Override
public boolean canDrag(#NonNull AppBarLayout appBarLayout) {
return mEnabled;
}
});
}
#Override
public boolean onStartNestedScroll(#NonNull CoordinatorLayout parent, #NonNull AppBarLayout child, #NonNull View directTargetChild, View target, int nestedScrollAxes, int type) {
return mEnabled && super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes, type);
}
public void setEnabled(boolean enabled) {
mEnabled = enabled;
}
public boolean isEnabled() {
return mEnabled;
}
}
In order to lock the app bar just dynamically set the minHeight of the CollapsingToolbarLayout within it. That will prevent it from being collapsed when you drag on it.

Hiding the ActionBar on RecyclerView/ListView onScroll

In my application I got an activity with some kind of actionbar at the top and the listview below it. What I want to do - is to scroll it UP with the list, so it hides and then, when the list is being scrolled down - it should scroll down with the list, like it was just over the upper screen border. how can i achieve this functionality?
Updated 6/3/2015:
Google now supports this using the CoordinatorLayout.
<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.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="#+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
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" />
</android.support.design.widget.CoordinatorLayout>
Source: https://github.com/chrisbanes/cheesesquare/blob/master/app/src/main/res/layout/include_list_viewpager.xml
Documented here: https://developer.android.com/reference/android/support/design/widget/AppBarLayout.html
Original Answer:
Example similar to Google Play Music and Umano apps:
https://github.com/umano/AndroidSlidingUpPanel
Take a look at the code in this repository. As you slide the panel up, the ActionBar slides up as well.
From the Demo:
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
SlidingUpPanelLayout layout = (SlidingUpPanelLayout) findViewById(R.id.sliding_layout);
layout.setShadowDrawable(getResources().getDrawable(R.drawable.above_shadow));
layout.setAnchorPoint(0.3f);
layout.setPanelSlideListener(new PanelSlideListener() {
#Override
public void onPanelSlide(View panel, float slideOffset) {
Log.i(TAG, "onPanelSlide, offset " + slideOffset);
if (slideOffset < 0.2) {
if (getActionBar().isShowing()) {
getActionBar().hide();
}
} else {
if (!getActionBar().isShowing()) {
getActionBar().show();
}
}
}
#Override
public void onPanelExpanded(View panel) {
Log.i(TAG, "onPanelExpanded");
}
#Override
public void onPanelCollapsed(View panel) {
Log.i(TAG, "onPanelCollapsed");
}
#Override
public void onPanelAnchored(View panel) {
Log.i(TAG, "onPanelAnchored");
}
});
Download example here:
https://play.google.com/store/apps/details?id=com.sothree.umano
ListView - without Libraries:
I recently wanted the same functionality and this works perfectly for me:
As the user scrolls upward, the ActionBar will be hidden in order to give the user the entire screen to work work with.
As the user scrolls downward and lets go, the ActionBar will return.
getWindow().requestFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
listView.setOnScrollListener(new OnScrollListener() {
int mLastFirstVisibleItem = 0;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) { }
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (view.getId() == listView.getId()) {
final int currentFirstVisibleItem = listView.getFirstVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
// getSherlockActivity().getSupportActionBar().hide();
getSupportActionBar().hide();
} else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
// getSherlockActivity().getSupportActionBar().show();
getSupportActionBar().show();
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
});
RecyclerView - without libraries
this.mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
int mLastFirstVisibleItem = 0;
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
final int currentFirstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
if (currentFirstVisibleItem > this.mLastFirstVisibleItem) {
MainActivity.this.getSupportActionBar().hide();
} else if (currentFirstVisibleItem < this.mLastFirstVisibleItem) {
MainActivity.this.getSupportActionBar().show();
}
this.mLastFirstVisibleItem = currentFirstVisibleItem;
}
});
Let me know if you need anymore help!
You experience the flickering since by hiding/showing the ActionBar the available space for your content layout changes, which causes a relayout. With this the index of the first visible item's position changes as well (you can verify this by logging out mLastFirstVisibleItem and currentFirstVisibleItem.
You can cope with the flickering by letting the ActionBar overlay your content layout. To enable overlay mode for the action bar, you need to create a custom theme that extends an existing action bar theme and set the android:windowActionBarOverlay property to true.
With this you can eliminate the flickering but the action bar will overlay your listview's content. An easy solution to this is to set the listview's (or the root layout's) top padding to the action bar's height.
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?android:attr/actionBarSize" />
Unfortunately this will result in a constant padding at the top. A refinement of this solution is to add a header view to the list view which has the height of ?android:attr/actionBarSize (and remove the top padding set previously)
What you are looking for is called Quick Return pattern, applied to the Action Bar. Google IO 2014 app use exactly that. I use it in one of my apps, you can check the source code of that Google app to see how they got it. The BaseActivity class is where you have what you need, read the code and extract just that specific functionality. Enjoy coding! :)
I am sure that it's not the best solution. But I haven't found a better one yet. Hope it will be helpfull.
static boolean scroll_down;
...
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (scroll_down) {
actionBar.hide();
} else {
actionBar.show();
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dy > 70) {
//scroll down
scroll_down = true;
} else if (dy < -5) {
//scroll up
scroll_down = false;
}
}
});
I have a thought that it is a good idea.
listView.setOnScrollListener(new OnScrollListener() {
int mLastFirstVisibleItem = 0;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_FLING:
if (view.getId()==lv_searchresult_results.getId()){
final int currentFirstVisibleItem=lv_searchresult_results.getFirstVisiblePosition();
if(currentFirstVisibleItem>mLastFirstVisibleItem){
ObjectAnimator.ofFloat(toolbar, "translationY", -toolbar.getHeight()).start();
else if(currentFirstVisibleItem<(mLastFirstVisibleItem)){
ObjectAnimator.ofFloat(toolbar, "translationY", 0).start();
}
mLastFirstVisibleItem= currentFirstVisibleItem;
}
if(lv_searchresult_results.getLastVisiblePosition() == myAdapter.getListMap().size()){
if(myAdapter.getListMap().size() < allRows&&!upLoading){
}
}
break;
case OnScrollListener.SCROLL_STATE_IDLE:
if (view.getFirstVisiblePosition()<=1){
ObjectAnimator.ofFloat(toolbar, "translationY", 0).start();
}
if(lv_searchresult_results.getLastVisiblePosition() == myAdapter.getListMap().size()){
if(myAdapter.getListMap().size() < allRows&&!upLoading){
}
}
break;
I think it will work fine.....
listView.setOnScrollListener(new OnScrollListener() {
int mLastFirstVisibleItem = listView.getLastVisiblePosition();
boolean isScrolling = false;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if(scrollState == 0)
isScrolling = false;
else if(scrollState == 1)
isScrolling = true;
else if(scrollState == 2)
isScrolling = true;
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (view.getId() == myTeamListView.getId()) {
if(isScrolling) {
final int currentFirstVisibleItem = myTeamListView.getLastVisiblePosition();
if (currentFirstVisibleItem > mLastFirstVisibleItem) {
((AppCompatActivity)getActivity()).getSupportActionBar().hide();
} else if (currentFirstVisibleItem < mLastFirstVisibleItem) {
((AppCompatActivity)getActivity()).getSupportActionBar().show();
}
mLastFirstVisibleItem = currentFirstVisibleItem;
}
}
}
});
If you are using a CoordinatorLayout, here is the trick.
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways" />
The app:layout_scrollFlags="scroll|enterAlways" line will cause our Toolbar to scroll off the screen when the user scrolls down the list and as soon as he starts scrolling up the Toolbar will show up again.

Categories

Resources