I have a bottom sheet that has inside a listview, I don't want to have the behaviour to expand or to dismiss, I just want to keep it at the fixed size, Now it shows at the size I want (70% of screen) but on slide up or down it changes its state.
Is there a way i can do this?
I have tried:
BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
// dismiss();
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
};
// bottomSheetBehavior.setBottomSheetCallback(bottomSheetCallback);
if (behavior != null && behavior instanceof BottomSheetBehavior) {
((BottomSheetBehavior) behavior).setBottomSheetCallback(bottomSheetCallback);
((BottomSheetBehavior) behavior).setPeekHeight(((RestaurantsActivity) getActivity()).getHeightForFragment());
}
I am trying to fix the expanded height of the sliding panel. What I want is that the sliding panel layout can be expanded only to 40 or 50% of screen height. I tried adding umanoAnchorPoint attribute with value 0.4 but the view can still be dragged to full screen.
You may use the sliding listener like this:
slidingUpPanelLayout.addPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
#Override
public void onPanelSlide(View panel, float slideOffset) {
}
#Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
if(newState == SlidingUpPanelLayout.PanelState.EXPANDED) slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.ANCHORED);
}
});
or maybe:
slidingUpPanelLayout.addPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
#Override
public void onPanelSlide(View panel, float slideOffset) {
if(slideOffset > desiredOffset) slidingUpPanelLayout.setPanelState(SlidingUpPanelLayout.PanelState.ANCHORED);
}
#Override
public void onPanelStateChanged(View panel, SlidingUpPanelLayout.PanelState previousState, SlidingUpPanelLayout.PanelState newState) {
}
});
I have the FAB working when recyclerview has enough items to scroll, but i need to handle the case when recyclerview does not scroll (the total of items do not cover the screen).
At the moment this is how I handle the scroll:
public class FABBehavior extends FloatingActionButton.Behavior {
public FABBehavior() {
super();
}
public FABBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
#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) {
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
int fab_bottomMargin = layoutParams.bottomMargin;
child.animate().translationY(child.getHeight() + fab_bottomMargin).setInterpolator(new LinearInterpolator()).start();
} else if (dyConsumed < 0) {
child.animate().translationY(0).setInterpolator(new LinearInterpolator()).start();
}
}
#Override
public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, final View directTargetChild, final View target, final int nestedScrollAxes) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
}
How to handle when recyclerview has few items?
You have to handle another case independently from CoordinatorLayout.
override a function layoutDependsOn:
#Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return super.layoutDependsOn(parent, child, dependency) || dependency instanceof RecyclerView;
}
onNestedScroll should also handle another case:
if (target instanceof RecyclerView) {
handleRecyclerViewScrolling(target, child);
return;
}
handleRecyclerViewScrolling should look like:
private void handleRecyclerViewScrolling(View target, FloatingActionButton child) {
if (scrollListener != null) {
return;
}
RecyclerView recyclerView = (RecyclerView) target;
scrollListener = new RecyclerViewScrollListener(child);
recyclerView.addOnScrollListener(scrollListener);
}
scrollListener should be a field in your FABBehavior class. Also declare inner class inside FABBehavior:
private class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
FloatingActionButton mChild;
public RecyclerViewScrollListener(FloatingActionButton child) {
this.mChild = child;
}
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
mChild.show();
} else {
mChild.hide();
}
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (!recyclerView.canScrollVertically(Integer.MAX_VALUE)) {
mChild.show();
}
}
}
RecyclerViewScrollListener hides FAB, when it is scrolling and shows it when it is in idle state.
I know how to change the height of a Bottom Sheet.
There is no problem to increase the height of a bottom sheet. However I cannot decrease its height with the following code .
bottomSheetBehavior.setPeekHeight(peekHeight); // peekHeight < previous height
bottomSheetBehavior.setState(STATE_COLLAPSED);
Anyone encountered the same problem ?
I am trying to do the same and I don't have such problem. I could easily increase and decrease my BottomSheetDialogFragment height. Here is my code of two methods of my fragment:
private BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() {
#Override
public void onStateChanged(#NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
#Override
public void onSlide(#NonNull View bottomSheet, float slideOffset) {
}
};
public static BottomSheetFragment newInstance() {
return new BottomSheetFragment();
}
#Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.bottom_sheet_dialog_content_view, null);
dialog.setContentView(contentView);
CoordinatorLayout.LayoutParams layoutParams = ((CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams());
CoordinatorLayout.Behavior behavior = layoutParams.getBehavior();
if (behavior != null && behavior instanceof BottomSheetBehavior) {
((BottomSheetBehavior) behavior).setBottomSheetCallback(bottomSheetCallback);
((BottomSheetBehavior) behavior).setPeekHeight(getResources().getDimensionPixelSize(R.dimen.bottom_sheet_height) / 4);
}
initRecyclerView(contentView);
}
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()
}
}
})