自定义view支持behavior让 FloatingActionMenu支持和 FloatingActionBootn一样的功能支持snakebar控制不遮挡,滚动隐藏显示.behavior精通.

首选实现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方可达到效果.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值