Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save suclike/b16f21c4730347f55852a7f90282cce6 to your computer and use it in GitHub Desktop.

Select an option

Save suclike/b16f21c4730347f55852a7f90282cce6 to your computer and use it in GitHub Desktop.
FAB behavior that lets FAB scroll out towards the bottom in sync with AppBarLayout scrolling out towards the top
/**
* Behavior for FABs that does not support anchoring to AppBarLayout, but instead translates the FAB
* out of the bottom in sync with the AppBarLayout collapsing towards the top.
* <p>
* Extends FloatingActionButton.Behavior to keep using the pre-Lollipop shadow padding offset.
*/
public class AppBarBoundFabBehavior extends FloatingActionButton.Behavior {
public AppBarBoundFabBehavior(Context context, AttributeSet attrs) {
super();
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
if (dependency instanceof AppBarLayout) {
((AppBarLayout) dependency).addOnOffsetChangedListener(new FabOffsetter(parent, child));
}
return dependency instanceof AppBarLayout || super.layoutDependsOn(parent, child, dependency);
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
//noinspection SimplifiableIfStatement
if (dependency instanceof AppBarLayout) {
// if the dependency is an AppBarLayout, do not allow super to react on that
// we don't want that behavior
return true;
}
return super.onDependentViewChanged(parent, fab, dependency);
}
}
public class FabOffsetter implements AppBarLayout.OnOffsetChangedListener {
private final CoordinatorLayout parent;
private final FloatingActionButton fab;
public FabOffsetter(@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton child) {
this.parent = parent;
this.fab = child;
}
@Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
// fab should scroll out down in sync with the appBarLayout scrolling out up.
// let's see how far along the way the appBarLayout is
// (if displacementFraction == 0.0f then no displacement, appBar is fully expanded;
// if displacementFraction == 1.0f then full displacement, appBar is totally collapsed)
float displacementFraction = -verticalOffset / (float) appBarLayout.getHeight();
// need to separate translationY on the fab that comes from this behavior
// and one that comes from other sources
// translationY from this behavior is stored in a tag on the fab
float translationYFromThis = coalesce((Float) fab.getTag(R.id.fab_translationY_from_AppBarBoundFabBehavior), 0f);
// top position, accounting for translation not coming from this behavior
float topUntranslatedFromThis = fab.getTop() + fab.getTranslationY() - translationYFromThis;
// total length to displace by (from position uninfluenced by this behavior) for a full appBar collapse
float fullDisplacement = parent.getBottom() - topUntranslatedFromThis;
// calculate and store new value for displacement coming from this behavior
float newTranslationYFromThis = fullDisplacement * displacementFraction;
fab.setTag(R.id.fab_translationY_from_AppBarBoundFabBehavior, newTranslationYFromThis);
// update translation value by difference found in this step
fab.setTranslationY(newTranslationYFromThis - translationYFromThis + fab.getTranslationY());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OnOffsetChangedListener that = (OnOffsetChangedListener) o;
return parent.equals(that.parent) && fab.equals(that.fab);
}
@Override
public int hashCode() {
int result = parent.hashCode();
result = 31 * result + fab.hashCode();
return result;
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<item type="id" name="fab_translationY_from_AppBarBoundFabBehavior"/>
</resources>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment