Skip to content

Instantly share code, notes, and snippets.

@NikolaDespotoski
Last active November 19, 2015 11:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NikolaDespotoski/ee3fbbc08345583f5e99 to your computer and use it in GitHub Desktop.
Save NikolaDespotoski/ee3fbbc08345583f5e99 to your computer and use it in GitHub Desktop.
package samples.despotoski.nikola.com.appbarlayoutsample.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.annotation.IntDef;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListener;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Interpolator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Created by Nikola D. on 11/17/2015.
*/
public class RollingFloatingActionButtonBehavior extends FloatingActionButton.Behavior {
private static final Interpolator FAST_OUT_SLOW_IN_INTERPOLATOR = new FastOutLinearInInterpolator();
private static final float TENSION_THRESHOLD = 0.5f;
private int mTotalDy;
private static final int ROLLING_OUT = -1;
private static final int ROLLING_IN = 0;
private static final int ROLLED_OUT = 1;
private static final int IDLE = 2;
private int mRollingState = IDLE;
private boolean mTensionFlag = false;
private int mOffscreenTranslation;
private float mTensionFactor = 0f;
private long mTimeInitial;
private float mCurrentSpeed;
public int getRollingFabState() {
return mRollingState;
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({IDLE, ROLLING_OUT, ROLLING_IN, ROLLED_OUT})
private @interface RollingFabState {
}
public RollingFloatingActionButtonBehavior() {
super();
}
public RollingFloatingActionButtonBehavior(Context context, AttributeSet attrs) {
}
private ViewPropertyAnimatorListener mRollingOutListener = new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationStart(View view) {
setRollingState(ROLLING_OUT);
}
@Override
public void onAnimationEnd(View view) {
setRollingState(ROLLED_OUT);
}
};
private ViewPropertyAnimatorListener mRollingInListener = new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationStart(View view) {
setRollingState(ROLLING_IN);
}
@Override
public void onAnimationEnd(View view) {
setRollingState(IDLE);
}
};
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
return super.layoutDependsOn(parent, child, dependency) || dependency instanceof NestedScrollingChild;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, float velocityX, float velocityY) {
if (Math.abs(velocityY) < Math.abs(velocityX)) return false;
if (velocityY < 0 && getRollingFabState() == ROLLED_OUT) {
postRollFabInCompletely(child);
} else if (velocityY > 0 && getRollingFabState() != IDLE) {
postRollFabOutCompletely(child);
}
return false;
}
private void postRollFabOutCompletely(final FloatingActionButton fab) {
ViewCompat.postOnAnimation(fab, new Runnable() {
@Override
public void run() {
rollOutFabCompletely(fab);
}
});
}
private void postRollFabInCompletely(final FloatingActionButton fab) {
ViewCompat.postOnAnimation(fab, new Runnable() {
@Override
public void run() {
mTensionFactor = 0f;
rollInFabCompletely(fab);
}
});
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dx, int dy, int[] consumed) {
if (dy > 0 && mTotalDy < 0 || dy < 0 && mTotalDy > 0) {
mTotalDy = 0;
}
mTotalDy += dy;
if (mTotalDy >= child.getHeight() && getRollingFabState() == IDLE) {
float rollBy = mTotalDy - child.getHeight() / child.getHeight();
rollOutFabBy(child, rollBy);
} else if (mTotalDy < -child.getHeight()) {
if (getRollingFabState() == ROLLED_OUT) {
rollInFabCompletely(child);
} else if (getRollingFabState() == ROLLING_OUT) {
ViewCompat.animate(child).cancel();
rollInFabCompletely(child);
}
}
}
private void rollInFabCompletely(FloatingActionButton child) {
ViewCompat.animate(child).translationX(0f).translationY(0f).rotation(0).setDuration(200)
.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR).setListener(mRollingInListener).start();
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target) {
if (mTensionFactor > 0 && mTensionFactor <= 0.5)
postRollFabInCompletely(child);
else if (getRollingFabState() == IDLE && mTensionFactor >= 0.5)
postRollFabOutCompletely(child);
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
int layoutDirection) {
boolean superLayout = super.onLayoutChild(parent, child, layoutDirection);
float center = child.getWidth() / 2;
ViewCompat.setPivotX(child, center);
ViewCompat.setPivotY(child, center);
mOffscreenTranslation = child.getWidth() + child.getWidth() / 2;
return superLayout;
}
private void rollOutFabBy(final FloatingActionButton child, float rollBy) {
float offScreen = Math.abs(child.getWidth() * rollBy);
if (offScreen <= child.getWidth() * TENSION_THRESHOLD) {
ViewCompat.setRotation(child, 360 * rollBy);
ViewCompat.setTranslationX(child, offScreen);
ViewCompat.setTranslationY(child, offScreen);
mTensionFlag = true;
mTensionFactor = rollBy;
} else {
mTensionFlag = false;
postRollFabOutCompletely(child);
}
}
private void rollOutFabCompletely(FloatingActionButton child) {
ViewCompat.animate(child).translationX(mOffscreenTranslation).translationY(mOffscreenTranslation).rotation(360).setDuration(200).setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR).setListener(mRollingOutListener).start();
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, FloatingActionButton child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout || dependency instanceof AppBarLayout) {
super.onDependentViewChanged(parent, child, dependency);
}
return false;
}
public void setRollingState(@RollingFabState int rollingState) {
this.mRollingState = rollingState;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment