I am working on app in some fragment i want to hide FloatingActionButtton.
When i set android:visibility="gone". Behavior animation show me FloatingActionButtton when i swipe up and down. is there is any way i can disable/enable FloatingActionButtton behavior.
Thank you advance.
here is my code
QuickReturnFooterBehavior.java
package com.app.common;
import android.animation.Animator;
import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewPropertyAnimator;
#SuppressWarnings("unused")
public class QuickReturnFooterBehavior extends CoordinatorLayout.Behavior<View> {
private static final FastOutSlowInInterpolator INTERPOLATOR = new FastOutSlowInInterpolator();
private int mDySinceDirectionChange;
private boolean mIsShowing;
private boolean mIsHiding;
public QuickReturnFooterBehavior() {
}
public QuickReturnFooterBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
#Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
if (dy > 0 && mDySinceDirectionChange < 0
|| dy < 0 && mDySinceDirectionChange > 0) {
// We detected a direction change- cancel existing animations and reset our cumulative delta Y
child.animate().cancel();
mDySinceDirectionChange = 0;
}
mDySinceDirectionChange += dy;
if (mDySinceDirectionChange > child.getHeight()
&& child.getVisibility() == View.VISIBLE
&& !mIsHiding) {
hide(child);
} else if (mDySinceDirectionChange < 0
&& child.getVisibility() == View.GONE
&& !mIsShowing) {
show(child);
}
}
/**
* Hide the quick return view.
*
* Animates hiding the view, with the view sliding down and out of the screen.
* After the view has disappeared, its visibility will change to GONE.
*
* #param view The quick return view
*/
private void hide(final View view) {
mIsHiding = true;
ViewPropertyAnimator animator = view.animate()
.translationY(view.getHeight())
.setInterpolator(INTERPOLATOR)
.setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {}
#Override
public void onAnimationEnd(Animator animator) {
// Prevent drawing the View after it is gone
mIsHiding = false;
view.setVisibility(View.GONE);
}
#Override
public void onAnimationCancel(Animator animator) {
// Canceling a hide should show the view
mIsHiding = false;
if (!mIsShowing) {
show(view);
}
}
#Override
public void onAnimationRepeat(Animator animator) {}
});
animator.start();
}
/**
* Show the quick return view.
*
* Animates showing the view, with the view sliding up from the bottom of the screen.
* After the view has reappeared, its visibility will change to VISIBLE.
*
* #param view The quick return view
*/
private void show(final View view) {
mIsShowing = true;
ViewPropertyAnimator animator = view.animate()
.translationY(0)
.setInterpolator(INTERPOLATOR)
.setDuration(200);
animator.setListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animator) {
view.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationEnd(Animator animator) {
mIsShowing = false;
}
#Override
public void onAnimationCancel(Animator animator) {
// Canceling a show should hide the view
mIsShowing = false;
if (!mIsHiding) {
hide(view);
}
}
#Override
public void onAnimationRepeat(Animator animator) {}
});
animator.start();
}
}
and XML
<android.support.design.widget.FloatingActionButton
app:layout_behavior="com.app.common.QuickReturnFooterBehavior"
android:id="#+id/fab_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:src="#drawable/ic_action_quick_response_code"
app:backgroundTint="#color/text_gray"
app:descriptionText="#string/add_friend"
app:elevation="3dp"
app:borderWidth="0dp"
/>
Finally I find it solution and I want to share with you.
You can enable/disable FloatingActionButton Behavior
Disable Behavior
FloatingActionButton fab2 = (FloatingActionButton)findViewById(R.id.fab2);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fab2.getLayoutParams();
params.setBehavior(null);
fab2.requestLayout();
fab2.setVisibility(View.GONE);
Enable Behavior
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fab2.getLayoutParams();
params.setBehavior(new QuickReturnFooterBehavior());
fab2.requestLayout();
fab2.setVisibility(View.VISIBLE);
Edited: More Reusable Class
public class CoordinateBehaviourUtils {
public static void enableDisableViewBehaviour(View view,CoordinatorLayout.Behavior<View> behavior,boolean enable){
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) view.getLayoutParams();
params.setBehavior(behavior);
view.requestLayout();
view.setVisibility((enable ? View.VISIBLE: View.GONE));
}
}
How To Enable Using Common Class
FloatingActionButton fab2 = (FloatingActionButton)findViewById(R.id.fab2);
CoordinateBehaviourUtils.enableDisableViewBehaviour(fab2,new QuickReturnFooterBehavior(),true);
How To Disable Using Common Class
FloatingActionButton fab2 = (FloatingActionButton)findViewById(R.id.fab2);
CoordinateBehaviourUtils.enableDisableViewBehaviour(fab2,null,false);
Hope it will solve your problem :)
There is no difference while setting up the visibility of a FAB, it works likely other controls.
0 is for VISIBLE
4 is for INVISIBLE
8 is for GONE
You can try something this;
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
// to make is disable for some requirement
fab.setVisibility(View.GONE);
// to make it enable
fab.setVisibility(View.VISIBLE);
Related
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;
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
}
}
}
})
}
I'm creating a Q&A where each question is a card. The answer starts showing the first line, but when its clicked it should expanded to show the full answer.
When an answer is expanded/collapsed the rest of the RecyclerView should animate to make room for the expansion or collapse to avoid showing a blank space.
I watched the talk on RecyclerView animations, and believe I want a custom ItemAnimator, where I override animateChange. At that point I should create an ObjectAnimator to animate the height of the View's LayoutParams. Unfortunately I'm having a hard time tying it all together. I also return true when overriding canReuseUpdatedViewHolder, so we reuse the same viewholder.
#Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
#Override
public boolean animateChange(#NonNull RecyclerView.ViewHolder oldHolder,
#NonNull final RecyclerView.ViewHolder newHolder,
#NonNull ItemHolderInfo preInfo,
#NonNull ItemHolderInfo postInfo) {
Log.d("test", "Run custom animation.");
final ColorsAdapter.ColorViewHolder holder = (ColorsAdapter.ColorViewHolder) newHolder;
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) holder.tvColor.getLayoutParams();
ObjectAnimator halfSize = ObjectAnimator.ofInt(holder.tvColor.getLayoutParams(), "height", params.height, 0);
halfSize.start();
return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
}
Right now I'm just trying to get something to animate, but nothing happens... Any ideas?
I think your animation was not working because you cannot animate LayoutParams that way although it would be neat if you could. I tried the code you had and all it did was make my view jump to the new height. Only way I found to get this to work was to use a ValueAnimator as you can see in the example below.
I noticed some shortcomings when using the DefaultItemAnimator to show/hide a view by updating its visibility. Although it did make room for the new view and animated the rest of the items up and down based on the visibility of the expandable view, I noticed it did not animate the height of the expandable view. It simply faded into place and out of place using alpha value only.
Below is a custom ItemAnimator that has size and alpha animations based on hiding/showing a LinearLayout in the ViewHolder layout. It also allows the reuse of the same ViewHolder and attempts handling partial animations correctly if the user taps the header quickly:
public static class MyAnimator extends DefaultItemAnimator {
#Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, AnimatorState> animatorMap = new HashMap<>();
#Override
public boolean animateChange(#NonNull RecyclerView.ViewHolder oldHolder, #NonNull final RecyclerView.ViewHolder newHolder, #NonNull ItemHolderInfo preInfo, #NonNull ItemHolderInfo postInfo) {
final ValueAnimator heightAnim;
final ObjectAnimator alphaAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final View expandableView = vh.getExpandableView();
final int toHeight; // save height for later in case reversing animation
if(vh.isExpanded()) {
expandableView.setVisibility(View.VISIBLE);
// measure expandable view to get correct height
expandableView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
toHeight = expandableView.getMeasuredHeight();
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 1f);
} else {
toHeight = 0;
alphaAnim = ObjectAnimator.ofFloat(expandableView, "alpha", 0f);
}
heightAnim = ValueAnimator.ofInt(expandableView.getHeight(), toHeight);
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
expandableView.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
expandableView.requestLayout();
}
});
AnimatorSet animSet = new AnimatorSet()
.setDuration(getChangeDuration());
animSet.playTogether(heightAnim, alphaAnim);
animSet.addListener(new Animator.AnimatorListener() {
private boolean isCanceled;
#Override
public void onAnimationStart(Animator animation) { }
#Override
public void onAnimationEnd(Animator animation) {
if(!vh.isExpanded() && !isCanceled) {
expandableView.setVisibility(View.GONE);
}
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
#Override
public void onAnimationCancel(Animator animation) {
isCanceled = true;
}
#Override
public void onAnimationRepeat(Animator animation) { }
});
AnimatorState animatorState = animatorMap.get(newHolder);
if(animatorState != null) {
animatorState.animSet.cancel();
// animation already running. Set start current play time of
// new animations to keep them smooth for reverse animation
alphaAnim.setCurrentPlayTime(animatorState.alphaAnim.getCurrentPlayTime());
heightAnim.setCurrentPlayTime(animatorState.heightAnim.getCurrentPlayTime());
animatorMap.remove(newHolder);
}
animatorMap.put(newHolder, new AnimatorState(alphaAnim, heightAnim, animSet));
dispatchChangeStarting(newHolder, false);
animSet.start();
return false;
}
public static class AnimatorState {
final ValueAnimator alphaAnim, heightAnim;
final AnimatorSet animSet;
public AnimatorState(ValueAnimator alphaAnim, ValueAnimator heightAnim, AnimatorSet animSet) {
this.alphaAnim = alphaAnim;
this.heightAnim = heightAnim;
this.animSet = animSet;
}
}
}
This is the result using a slightly modified RecyclerView demo.
Update:
Just noticed your use case is actually a bit different after rereading the question. You have a text view and only want to show a single line of it and then later expand it to show all lines. Fortunately that simplifies the custom animator:
public static class MyAnimator extends DefaultItemAnimator {
#Override
public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
return true;
}
private HashMap<RecyclerView.ViewHolder, ValueAnimator> animatorMap = new HashMap<>();
#Override
public boolean animateChange(#NonNull RecyclerView.ViewHolder oldHolder, #NonNull final RecyclerView.ViewHolder newHolder, #NonNull ItemHolderInfo preInfo, #NonNull ItemHolderInfo postInfo) {
ValueAnimator prevAnim = animatorMap.get(newHolder);
if(prevAnim != null) {
prevAnim.reverse();
return false;
}
final ValueAnimator heightAnim;
final CustomAdapter.ViewHolder vh = (CustomAdapter.ViewHolder) newHolder;
final TextView tv = vh.getExpandableTextView();
if(vh.isExpanded()) {
tv.measure(View.MeasureSpec.makeMeasureSpec(((View) tv.getParent()).getWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.UNSPECIFIED);
heightAnim = ValueAnimator.ofInt(tv.getHeight(), tv.getMeasuredHeight());
} else {
Paint.FontMetrics fm = tv.getPaint().getFontMetrics();
heightAnim = ValueAnimator.ofInt(tv.getHeight(), (int)(Math.abs(fm.top) + Math.abs(fm.bottom)));
}
heightAnim.setDuration(getChangeDuration());
heightAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
tv.getLayoutParams().height = (Integer) heightAnim.getAnimatedValue();
tv.requestLayout();
}
});
heightAnim.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
dispatchChangeFinished(vh, false);
animatorMap.remove(newHolder);
}
#Override
public void onAnimationCancel(Animator animation) { }
#Override
public void onAnimationStart(Animator animation) { }
#Override
public void onAnimationRepeat(Animator animation) { }
});
animatorMap.put(newHolder, heightAnim);
dispatchChangeStarting(newHolder, false);
heightAnim.start();
return false;
}
}
And the new demo:
You don't have to implement a custom ItemAnimator the default DefaultItemAnimator already supports what you need. However you need to tell this Animator which views changed. I guess you are calling notifyDataSetChanged() in your adapter. This prevents the animation for a single changed item in the RecyclerView (in your case the expand/collapse of the item).
You should use notifyItemChanged(int position) for the items that were changed. Here is a short itemClicked(int position) method that expands/collapses views in the RecyclerView. The field expandedPosition keeps track of the currently expanded item:
private void itemClicked(int position) {
if (expandedPosition == -1) {
// selected first item
expandedPosition = position;
notifyItemChanged(position);
} else if (expandedPosition == position) {
// collapse currently expanded item
expandedPosition = -1;
notifyItemChanged(position);
} else {
// collapse previously expanded item and expand new item
int oldExpanded = expandedPosition;
expandedPosition = position;
notifyItemChanged(oldExpanded);
notifyItemChanged(position);
}
}
This is the result:
According the documentation, you need to return false in animateChange or call runPendingAnimations later. Try returning false.
http://developer.android.com/reference/android/support/v7/widget/RecyclerView.ItemAnimator.html
Try this class:
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.graphics.Paint;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.TextView;
/**
* Created by ankitagrawal on 2/14/16.
*/
public class AnimatedViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private int originalHeight = 0;
private boolean mIsViewExpanded = false;
private TextView textView;
// ..... CODE ..... //
public AnimatedViewHolder(View v) {
super(v);
v.setOnClickListener(this);
// Initialize other views, like TextView, ImageView, etc. here
// If isViewExpanded == false then set the visibility
// of whatever will be in the expanded to GONE
if (!mIsViewExpanded) {
// Set Views to View.GONE and .setEnabled(false)
textView.setLines(1);
}
}
#Override
public void onClick(final View view) {
// Declare a ValueAnimator object
ValueAnimator valueAnimator;
if(mIsViewExpanded) {
view.measure(View.MeasureSpec.makeMeasureSpec(((View) view.getParent()).getWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.UNSPECIFIED);
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(view.getHeight(), view.getMeasuredHeight());
} else {
Paint.FontMetrics fm = ((TextView)view).getPaint().getFontMetrics();
valueAnimator = ValueAnimator.ofInt(view.getHeight(), (int) (Math.abs(fm.top) + Math.abs(fm.bottom)));
mIsViewExpanded = true;
}
valueAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationEnd(Animator animation) {
}
#Override
public void onAnimationCancel(Animator animation) { }
#Override
public void onAnimationStart(Animator animation) { }
#Override
public void onAnimationRepeat(Animator animation) { }
});
valueAnimator.setDuration(200);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
view.getLayoutParams().height = (Integer) animation.getAnimatedValue();
view.requestLayout();
}
});
valueAnimator.start();
}
}
The Advantage of this approach is it only add animation to onClick event and that best suits your requirement.
adding animation to viewholder will be too burdensome to your requirement.
and itemAnimator as per doc are animation for layout out items so also not best suits your requirement.
For expand & collapse animation android there is github library for it.
ExpandableRecyclerView
1).Add dependencies in the build.gradle file
dependencies {
compile 'com.android.support:recyclerview-v7:22.2.0'
compile 'com.bignerdranch.android:expandablerecyclerview:1.0.3'
}
Image of Expand & Collapse Animation
2) Expand & Collapse animation for RecyclerView animation
public static class ExampleViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private int originalHeight = 0;
private boolean isViewExpanded = false;
private YourCustomView yourCustomView
public ExampleViewHolder(View v) {
super(v);
v.setOnClickListener(this);
// Initialize other views, like TextView, ImageView, etc. here
// If isViewExpanded == false then set the visibility
// of whatever will be in the expanded to GONE
if (isViewExpanded == false) {
// Set Views to View.GONE and .setEnabled(false)
yourCustomView.setVisibility(View.GONE);
yourCustomView.setEnabled(false);
}
}
#Override
public void onClick(final View view) {
// If the originalHeight is 0 then find the height of the View being used
// This would be the height of the cardview
if (originalHeight == 0) {
originalHeight = view.getHeight();
}
// Declare a ValueAnimator object
ValueAnimator valueAnimator;
if (!mIsViewExpanded) {
yourCustomView.setVisibility(View.VISIBLE);
yourCustomView.setEnabled(true);
mIsViewExpanded = true;
valueAnimator = ValueAnimator.ofInt(originalHeight, originalHeight + (int) (originalHeight * 2.0)); // These values in this method can be changed to expand however much you like
} else {
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(originalHeight + (int) (originalHeight * 2.0), originalHeight);
Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out
a.setDuration(200);
// Set a listener to the animation and configure onAnimationEnd
a.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
yourCustomView.setVisibility(View.INVISIBLE);
yourCustomView.setEnabled(false);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
// Set the animation on the custom view
yourCustomView.startAnimation(a);
}
valueAnimator.setDuration(200);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
view.getLayoutParams().height = value.intValue();
view.requestLayout();
}
});
valueAnimator.start();
}
}
Hope this will help you.
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()
}
}
})
I have a list of buttons. When I press a button, a View should slide in a downwards motion out of the button, like this:
Start:
Halfway:
End:
How would I go about this? The View that should slide out is bigger than the button, so first hiding the View behind the button and then sliding it downwards causes the View to be visible above the button. That should not happen.
Any ideas or examples on how to approach this?
I believe the simplest approach is to extend Animation class and override applyTransformation() to change the view's height as follows:
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.Transformation;
import android.widget.LinearLayout;
public class MyCustomAnimation extends Animation {
public final static int COLLAPSE = 1;
public final static int EXPAND = 0;
private View mView;
private int mEndHeight;
private int mType;
private LinearLayout.LayoutParams mLayoutParams;
public MyCustomAnimation(View view, int duration, int type) {
setDuration(duration);
mView = view;
mEndHeight = mView.getHeight();
mLayoutParams = ((LinearLayout.LayoutParams) view.getLayoutParams());
mType = type;
if(mType == EXPAND) {
mLayoutParams.height = 0;
} else {
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
}
view.setVisibility(View.VISIBLE);
}
public int getHeight(){
return mView.getHeight();
}
public void setHeight(int height){
mEndHeight = height;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
if(mType == EXPAND) {
mLayoutParams.height = (int)(mEndHeight * interpolatedTime);
} else {
mLayoutParams.height = (int) (mEndHeight * (1 - interpolatedTime));
}
mView.requestLayout();
} else {
if(mType == EXPAND) {
mLayoutParams.height = LayoutParams.WRAP_CONTENT;
mView.requestLayout();
}else{
mView.setVisibility(View.GONE);
}
}
}
}
To use it, set your onclick() as follows:
int height;
#Override
public void onClick(View v) {
if(view2.getVisibility() == View.VISIBLE){
MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.COLLAPSE);
height = a.getHeight();
view2.startAnimation(a);
}else{
MyCustomAnimation a = new MyCustomAnimation(view2, 1000, MyCustomAnimation.EXPAND);
a.setHeight(height);
view2.startAnimation(a);
}
}
Regards.
Use something like:
Animation a = new ScaleAnimation(1, 1, 0, 1, Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0);
a.setFillAfter(true);
view.setAnimation(a);
a.setDuration(1000);
view.startAnimation(a);
Here is simple example of hand-made animation, that provide what you want. It works in test app, but I'm not sure that there is no bugs:
public class MainActivity extends Activity implements OnClickListener {
private Timer timer;
private TimerTask animationTask;
private View view1;
private View view2;
boolean animating;
boolean increasing = true;
int initHeight = -1;
private LayoutParams params;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
timer = new Timer();
view1 = findViewById(R.id.view1);// clickable view
view1.setOnClickListener(this);
view2 = findViewById(R.id.view2);// animated view
params = view2.getLayoutParams();
}
#Override
protected void onDestroy() {
super.onDestroy();
timer.cancel();
}
#Override
public void onClick(View v) {
Toast.makeText(this, "start animating...", Toast.LENGTH_SHORT).show();
animationTask = new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (animationFinished()) {
animating = false;
cancel();//canceling animating task
return;
}
params.height += increasing ? 1 : -1;
view2.setLayoutParams(params);
}
});
}
private boolean animationFinished() {
int viewHeight = view2.getHeight();
if (increasing && viewHeight >= initHeight) {
return true;
}
if (!increasing && viewHeight <= 0) {
return true;
}
return false;
}
};
//if we already animating - we just change direction of animation
increasing = !increasing;
if (!animating) {
animating = true;
int height = view2.getHeight();
params.height = height;
view2.setLayoutParams(params);//change param "height" from "wrap_conent" to real height
if (initHeight < 0) {//height of view - we setup it only once
initHeight = height;
}
timer.schedule(animationTask, 0, 10);//changing repeat time here will fasten or slow down animation
}
}
}
Maybe you can set the height to 0 and gradually increase the height. But then you will have the problem that you have to be sure your text is aligned at the bottom of the view. And also to know what the maximal height of the view should be.
use a sliding list adapter so much easier than messing around with animations
https://github.com/tjerkw/Android-SlideExpandableListView
Simply pass android:animateLayoutChanges to LinearLayout that holds all the views, you will achieve your desired result.
I would do it like that. First the layout for the whole collapsible panel component: (pseudo xml)
RelativeLayout (id=panel, clip)
LinearLayout (id=content, alignParentBottom=true)
LinearLayout (id=handle, above=content)
This should ensure that the content is always below the handle.
Then when you need to collapse:
Animate the top margin of content from 0 to -content.height
Animate the height of the panel from current to current-content.height