package com.bottomsheetbehavior; import android.content.Context; import android.support.annotation.NonNull; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.v4.view.ViewCompat; import android.support.v4.widget.NestedScrollView; import android.util.AttributeSet; import android.view.View; import java.lang.ref.WeakReference; /** ~ 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. ~ ~ https://github.com/miguelhincapie/CustomBottomSheetBehavior */ /** * This class only cares about hide or unhide the FAB because the anchor behavior is something * already in FAB. */ public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { /** * One of the point used to set hide() or show() in FAB */ private float offset; /** * The FAB should be hidden when it reach {@link #offset} or when {@link RNBottomSheetBehavior} * is visually lower than {@link RNBottomSheetBehavior#getPeekHeight()}. * We got a reference to the object to allow change dynamically PeekHeight in BottomSheet and * got updated here. */ private WeakReference<RNBottomSheetBehavior> mBottomSheetBehaviorRef; public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { super(); offset = 0; mBottomSheetBehaviorRef = null; } @Override public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, final View directTargetChild, final View target, final int nestedScrollAxes) { // Ensure we react to vertical scrolling return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL; } @Override public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) { if (dependency instanceof NestedScrollView) { try { RNBottomSheetBehavior.from(dependency); return true; } catch (IllegalArgumentException e){} } return false; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) { /** * Because we are not moving it, we always return false in this method. */ if (offset == 0) setOffsetValue(parent); if (mBottomSheetBehaviorRef == null) getBottomSheetBehavior(parent); int DyFix = getDyBetweenChildAndDependency(child, dependency); if ((child.getY() + DyFix) < offset) child.hide(); else if ((child.getY() + DyFix) >= offset) { /** * We are calculating every time point in Y where BottomSheet get {@link BottomSheetBehaviorGoogleMapsLike#STATE_COLLAPSED}. * If PeekHeight change dynamically we can reflect the behavior asap. */ if (mBottomSheetBehaviorRef == null || mBottomSheetBehaviorRef.get() == null) getBottomSheetBehavior(parent); int collapsedY = dependency.getHeight() - mBottomSheetBehaviorRef.get().getPeekHeight(); if ((child.getY() + DyFix) > collapsedY) child.hide(); else child.show(); } return false; } /** * In some <bold>WEIRD</bold> cases, mostly when you perform a little scroll but a fast one * the {@link #onDependentViewChanged(CoordinatorLayout, FloatingActionButton, View)} DOESN'T * reflect the real Y position of child mean the dependency get a better APROXIMATION of the real * Y. This was causing that FAB some times doesn't get unhidden. * @param child the FAB * @param dependency NestedScrollView instance * @return Dy betweens those 2 elements in Y, minus child's height/2 */ private int getDyBetweenChildAndDependency(@NonNull FloatingActionButton child, @NonNull View dependency) { if (dependency.getY() == 0 || dependency.getY() < offset) return 0; if ( (dependency.getY() - child.getY()) > child.getHeight() ) return Math.max(0, (int) ((dependency.getY() - (child.getHeight()/2)) - child.getY()) ); else return 0; } /** * Define one of the point in where the FAB should be hide when it reachs that point. * @param coordinatorLayout container of BottomSheet and AppBarLayout */ private void setOffsetValue(CoordinatorLayout coordinatorLayout) { for (int i = 0; i < coordinatorLayout.getChildCount(); i++) { View child = coordinatorLayout.getChildAt(i); if (child instanceof AppBarLayout) { if (child.getTag() != null && child.getTag().toString().contentEquals("modal-appbar") ) { offset = child.getY()+child.getHeight(); break; } } } } /** * Look into the CoordiantorLayout for the {@link RNBottomSheetBehavior} * @param coordinatorLayout with app:layout_behavior= {@link RNBottomSheetBehavior} */ private void getBottomSheetBehavior(@NonNull CoordinatorLayout coordinatorLayout) { for (int i = 0; i < coordinatorLayout.getChildCount(); i++) { View child = coordinatorLayout.getChildAt(i); if (child instanceof NestedScrollView) { try { RNBottomSheetBehavior temp = RNBottomSheetBehavior.from(child); mBottomSheetBehaviorRef = new WeakReference<>(temp); break; } catch (IllegalArgumentException e){} } } } }