Skip to content

Instantly share code, notes, and snippets.

@Smurph82
Last active May 15, 2016 13:13
Show Gist options
  • Save Smurph82/cb01071d2fe0b8b00ab4 to your computer and use it in GitHub Desktop.
Save Smurph82/cb01071d2fe0b8b00ab4 to your computer and use it in GitHub Desktop.
Animate Floating Action Button Android L. This will give your floating action button an animation similar to the Android Google+ app when scrolling on a RecyclerView.
/**
* Copyright (c) 2014 Ben Murphy (Smurph82)
* Created - Aug 19, 2014
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import com.humbhi.xcriptionsasc.BuildConfig;
public class FloatingBtnAnimaControl extends RecyclerView.OnScrollListener {
/**
* This interface will give the RecyclerView callbacks after the animation has started.
*/
public interface OnScrollingListener {
void onScrolled(RecyclerView recyclerView, int dx, int dy);
void onScrollStateChanged(RecyclerView recyclerView, int newState);
}
public OnScrollingListener mListener;
public void setOnScrollingListener(OnScrollingListener l) {
mListener = l;
}
/**
* Constructor
* @param button The {@link View} you want to animate on and off the screen while scrolling
*/
public FloatingBtnAnimaControl(View button) {
if (button==null) {
Log.w(tag, "View is null, nothing will be animated");
}
mButton = button;
}
/**
* Set the duration of the animation
* @param duration the duration in milliseconds</br>Default is 175
*/
public void setAnimationDuration(long duration) {
ANIMATION_DURATION = duration;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (mButton==null || dy==0) {
return;
}
if (dy>0) { // Scrolling to bottom
if (mIsScrollDirectionLocked && mScrollingDirection!=0) return;
if (mButton.getVisibility()==View.GONE || mIsAnimatingOff) {
return;
} else {
mScrollingDirection = SCROLLING_DOWN;
mIsAnimatingOff = !mIsAnimatingOff;
ViewCompat.setAlpha(mButton, 1f);
ViewCompat.setTranslationY(mButton, 0F);
ViewCompat.animate(mButton)
.alpha(0F)
.translationY(mButton.getHeight())
.setDuration(ANIMATION_DURATION)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
mIsAnimatingOff = !mIsAnimatingOff;
if (D) Log.d(tag, "Animation off screen Done!!!!");
mButton.setVisibility(View.GONE);
}
}).start();
}
} else { // Scrolling to top
if (mIsScrollDirectionLocked && mScrollingDirection!=0) return;
// if (mButton.getVisibility()==View.VISIBLE || mIsAnimatingOn) {
// return;
// } else {
if (mButton.getVisibility()!=View.VISIBLE && !mIsAnimatingOn) {
mScrollingDirection = SCROLLING_UP;
mIsAnimatingOn = !mIsAnimatingOn;
mButton.setVisibility(View.VISIBLE);
ViewCompat.setAlpha(mButton, 0F);
ViewCompat.setTranslationY(mButton, mButton.getHeight());
ViewCompat.animate(mButton)
.alpha(1F)
.translationY(0F)
.setDuration(ANIMATION_DURATION)
.setListener(new ViewPropertyAnimatorListenerAdapter() {
@Override
public void onAnimationEnd(View view) {
mIsAnimatingOn = !mIsAnimatingOn;
if (D) Log.d(tag, "Animation onto screen Done!!!!");
}
}).start();
}
}
if (mListener!=null) { mListener.onScrolled(recyclerView, dx, dy); }
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (!mIsScrollDirectionLocked) return;
switch (newState) {
case RecyclerView.SCROLL_STATE_IDLE:
mScrollingDirection = 0;
break;
default:
break;
}
if (mListener!=null) { mListener.onScrollStateChanged(recyclerView, newState); }
}
/**
* Returns the view that is being animated.
*/
public View getAnimatedView() { return mButton; }
/**
* This will lock the animation to the first direction that is scrolled.
* That means that when you scroll toward the bottom then scroll to
* the top without releasing the screen.
* The view will not be animated back on the screen.</br>
* You would have to release the screen and scroll the other direction
* to get the opposite animation.
*/
public void lockScrollingDirection() { mIsScrollDirectionLocked = true; }
/**
* Release the scroll direction lock so that a scroll direction
* change will immediately run the appropriate animation.
*/
public void unlockScrollingDirection() { mIsScrollDirectionLocked = false; }
/**
* Get the status of the current scroll lock.
*/
public boolean isScrollDirectionLocked() { return mIsScrollDirectionLocked; }
private static final int SCROLLING_UP = 1;
private static final int SCROLLING_DOWN = 2;
private int mScrollingDirection = 0;
private boolean mIsScrollDirectionLocked = false;
private long ANIMATION_DURATION = 175L;
private boolean mIsAnimatingOff = false;
private boolean mIsAnimatingOn = false;
private View mButton;
private boolean D = BuildConfig.DEBUG;
private static final String tag = FloatingBtnAnimaControl.class.getSimpleName();
}
/**
* Copyright (c) 2014 Ben Murphy (Smurph82)
* Created - Aug 19, 2014
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class MainFragment extends Fragment {
private FloatingBtnAnimaControl mBtnAnimaControl;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
...
mBtnAnimaControl = new FloatingBtnAnimaControl(mBtnAdd);
// Lock Scrolling is optional. Unlocked by default
mBtnAnimaControl.lockScrollingDirection();
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
mRecyclerView.addOnScrollListener(mBtnAnimaControl);
}
}
@Smurph82
Copy link
Author

Revision 8 and 9

  • As per request I am now using the ViewCompat to handle the View animations.
  • Implemented the RecyclerView.OnScrollListener in the helper class. This way you can just use the class instead of calling the helper from your own OnScrollListener.
  • Created a OnScrollingListener in the helper class. If implemented will be called after the animation is completed. My change to before animation not sure yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment