首选实现CoordinatorLayout.AttachedBehavior
然后创建一个自己的behavior,
import com.github.clans.fab.FloatingActionMenu;
import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.internal.DescendantOffsetUtils;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
/**
* 让第三方fam 支持嵌套动画。
*/
public class MyFloatingActionMenu extends FloatingActionMenu implements CoordinatorLayout.AttachedBehavior {
public MyFloatingActionMenu(Context context) {
super(context);
}
public MyFloatingActionMenu(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyFloatingActionMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public static class Behavior extends BaseBehavior<MyFloatingActionMenu> {
public Behavior() {
super();
}
}
@NonNull
@Override
public CoordinatorLayout.Behavior getBehavior() {
return new Behavior();
}
public static class BaseBehavior<T extends MyFloatingActionMenu>
extends CoordinatorLayout.Behavior<T> {
private static final boolean AUTO_HIDE_DEFAULT = true;
private Rect tmpRect;
private boolean autoHideEnabled;
public BaseBehavior() {
super();
autoHideEnabled = AUTO_HIDE_DEFAULT;
}
/**
* Sets whether the associated MyFloatingActionMenu automatically hides when there is not enough
* space to be displayed. This works with {@link AppBarLayout} and {@link BottomSheetBehavior}.
*
* @attr ref
* com.google.android.material.R.styleable#MyFloatingActionMenu_Behavior_Layout_behavior_autoHide
* @param autoHide true to enable automatic hiding
*/
public void setAutoHideEnabled(boolean autoHide) {
autoHideEnabled = autoHide;
}
/**
* Returns whether the associated MyFloatingActionMenu automatically hides when there is not
* enough space to be displayed.
*
* @attr ref
* com.google.android.material.R.styleable#MyFloatingActionMenu_Behavior_Layout_behavior_autoHide
* @return true if enabled
*/
public boolean isAutoHideEnabled() {
return autoHideEnabled;
}
@Override
public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams lp) {
if (lp.dodgeInsetEdges == Gravity.NO_GRAVITY) {
// If the developer hasn't set dodgeInsetEdges, lets set it to BOTTOM so that
// we dodge any Snackbars
lp.dodgeInsetEdges = Gravity.BOTTOM;
}
}
@Override
public boolean onDependentViewChanged(
CoordinatorLayout parent, @NonNull MyFloatingActionMenu child, View dependency) {
if (dependency instanceof AppBarLayout) {
// If we're depending on an AppBarLayout we will show/hide it automatically
// if the FAB is anchored to the AppBarLayout
updateFabVisibilityForAppBarLayout(parent, (AppBarLayout) dependency, child);
} else if (isBottomSheet(dependency)) {
updateFabVisibilityForBottomSheet(dependency, child);
}
return false;
}
private static boolean isBottomSheet(@NonNull View view) {
final ViewGroup.LayoutParams lp = view.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
return ((CoordinatorLayout.LayoutParams) lp).getBehavior() instanceof BottomSheetBehavior;
}
return false;
}
@SuppressLint("RestrictedApi")
private boolean shouldUpdateVisibility(
@NonNull View dependency, @NonNull MyFloatingActionMenu child) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) child.getLayoutParams();
if (!autoHideEnabled) {
return false;
}
if (lp.getAnchorId() != dependency.getId()) {
// The anchor ID doesn't match the dependency, so we won't automatically
// show/hide the FAB
return false;
}
//noinspection RedundantIfStatement
if (child.getVisibility() != VISIBLE) {
// The view isn't set to be visible so skip changing its visibility
return false;
}
return true;
}
@SuppressLint("RestrictedApi")
private boolean updateFabVisibilityForAppBarLayout(
CoordinatorLayout parent,
@NonNull AppBarLayout appBarLayout,
@NonNull MyFloatingActionMenu child) {
if (!shouldUpdateVisibility(appBarLayout, child)) {
return false;
}
if (tmpRect == null) {
tmpRect = new Rect();
}
// First, let's get the visible rect of the dependency
final Rect rect = tmpRect;
DescendantOffsetUtils.getDescendantRect(parent, appBarLayout, rect);
if (rect.bottom <= appBarLayout.getMinimumHeightForVisibleOverlappingContent()) {
// If the anchor's bottom is below the seam, we'll animate our FAB out
child.hideMenu( false);
} else {
// Else, we'll animate our FAB back in
child.showMenu( false);
}
return true;
}
private boolean updateFabVisibilityForBottomSheet(
@NonNull View bottomSheet, @NonNull MyFloatingActionMenu child) {
if (!shouldUpdateVisibility(bottomSheet, child)) {
return false;
}
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
if (bottomSheet.getTop() < child.getHeight() / 2 + lp.topMargin) {
child.hideMenu( false);
} else {
child.showMenu( false);
}
return true;
}
@Override
public boolean onLayoutChild(
@NonNull CoordinatorLayout parent,
@NonNull MyFloatingActionMenu child,
int layoutDirection) {
// First, let's make sure that the visibility of the FAB is consistent
final List<View> dependencies = parent.getDependencies(child);
for (int i = 0, count = dependencies.size(); i < count; i++) {
final View dependency = dependencies.get(i);
if (dependency instanceof AppBarLayout) {
if (updateFabVisibilityForAppBarLayout(parent, (AppBarLayout) dependency, child)) {
break;
}
} else if (isBottomSheet(dependency)) {
if (updateFabVisibilityForBottomSheet(dependency, child)) {
break;
}
}
}
// Now let the CoordinatorLayout lay out the FAB
parent.onLayoutChild(child, layoutDirection);
// Now offset it if needed
offsetIfNeeded(parent, child);
return true;
}
@Override
public boolean getInsetDodgeRect(
@NonNull CoordinatorLayout parent,
@NonNull MyFloatingActionMenu child,
@NonNull Rect rect) {
// Since we offset so that any internal shadow padding isn't shown, we need to make
// sure that the shadow isn't used for any dodge inset calculations
final Rect shadowPadding = new Rect();//TODO 阴影边距
rect.set(
child.getLeft() + shadowPadding.left,
child.getTop() + shadowPadding.top,
child.getRight() - shadowPadding.right,
child.getBottom() - shadowPadding.bottom);
return true;
}
/**
* Pre-Lollipop we use padding so that the shadow has enough space to be drawn. This method
* offsets our layout position so that we're positioned correctly if we're on one of our
* parent's edges.
*/
private void offsetIfNeeded(
@NonNull CoordinatorLayout parent, @NonNull MyFloatingActionMenu fab) {
final Rect padding = new Rect();//todo shadwo padding
if (padding != null && padding.centerX() > 0 && padding.centerY() > 0) {
final CoordinatorLayout.LayoutParams lp =
(CoordinatorLayout.LayoutParams) fab.getLayoutParams();
int offsetTB = 0;
int offsetLR = 0;
if (fab.getRight() >= parent.getWidth() - lp.rightMargin) {
// If we're on the right edge, shift it the right
offsetLR = padding.right;
} else if (fab.getLeft() <= lp.leftMargin) {
// If we're on the left edge, shift it the left
offsetLR = -padding.left;
}
if (fab.getBottom() >= parent.getHeight() - lp.bottomMargin) {
// If we're on the bottom edge, shift it down
offsetTB = padding.bottom;
} else if (fab.getTop() <= lp.topMargin) {
// If we're on the top edge, shift it up
offsetTB = -padding.top;
}
if (offsetTB != 0) {
ViewCompat.offsetTopAndBottom(fab, offsetTB);
}
if (offsetLR != 0) {
ViewCompat.offsetLeftAndRight(fab, offsetLR);
}
}
}
}
}
实现FabBehavior
public class FabBehavior extends MyFloatingActionMenu.Behavior {
public FabBehavior(Context context, AttributeSet attrs) {
super();
}
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull MyFloatingActionMenu child, @NonNull View directTargetChild, @NonNull View target, int nestedScrollAxes, int type) {
// return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes, type);
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild,
target, nestedScrollAxes,type);
}
/*
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
FloatingActionButton child, View directTargetChild, View target,
int nestedScrollAxes) {
}*/
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull MyFloatingActionMenu child, @NonNull View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
/**
* 对孩子依赖性观点的改变做出反应
* 每当依赖视图的大小或位置在标准布局流之外发生变化时,就会调用此方法。行为可以使用此方法来适当地更新子视图。
* 一个视图的依赖是由layoutDependsOn(CoordinatorLayout, view, view)决定的,或者其子视图是否设置了另一个视图作为它的锚点。
* 注意,如果行为通过这个方法改变了子元素的布局,那么它也应该能够在onLayoutChild中重建正确的位置。onDependentViewChanged将不会在正常布局期间被调用,因为每个子视图的布局总是按照依赖顺序发生。
* 如果Behavior改变了子视图的大小或位置,它应该返回true。默认实现返回false。
* 参数:
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, @NonNull MyFloatingActionMenu child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull MyFloatingActionMenu child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, @NonNull int[] consumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);
if(BuildConfig.DEBUG){
Log.w("FABMENU_Behavior","dyConsumed:"+dyConsumed+",visiable:"+visible);
}
if (dyConsumed > 0 && visible) {
//before show
visible = false;
onHide(child, null);
} else if (dyConsumed < 0) {
//before hide
visible = true;
onShow(child, null);
}
}
public void onHide(MyFloatingActionMenu fab, Toolbar toolbar) {
// toolbar.animate().translationY(-toolbar.getHeight()).setInterpolator(new AccelerateInterpolator(3));
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
fab.animate().translationY(fab.getHeight() + layoutParams.bottomMargin).setInterpolator(new AccelerateInterpolator(3));
}
public void onShow(MyFloatingActionMenu fab, Toolbar toolbar) {
// toolbar.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
fab.animate().translationY(0).setInterpolator(new DecelerateInterpolator(3));
}
}
xml设置behavior
<.thrid.MyFloatingActionMenu
android:id="@+id/fab_menu"
app:layout_behavior="app.ui.behavior.FabBehavior"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:paddingRight="10dp"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
app:menu_labels_ellipsize="end"
@@ -338,8 +354,7 @@
app:fab_size="mini"
app:fab_label="@string/all" />
为了测试效果,请根目录设置coorlayout 以及放一个recycleview进去,或者自己手写一个,实现了NestedChild否则无法测试效果.
要实现recyclerview或者自己的view滚动来控制fab的隐藏显示,就需要实现Nested方法,而recyclerview默认已经实现,对于自己的view,想知道如何实现,请看我另外一篇文章.
然后改fab同样也要实现CoordinatorLayout的一些方法,
然后还要自定义一个behavior方可达到效果.