BottomSheetBehavior+ViewPager+多R

作者: ZEKI安卓学弟 | 来源:发表于2020-08-21 17:48 被阅读0次

    最终效果

    滑动冲突_1.gif
    滑动冲突_2.gif

    使用BottomSheetBehavior引发的问题

    问题1:BottomSheetBehavior+ViewPager+多页RecyclerView组合,只有第一页列表可滑动

    CoordinatorLayout中对弹出的ViewGroup直接使用 com.google.android.material.bottomsheet.BottomSheetBehavior,本身是没有问题的,但当我们嵌套了ViewPager+多页RecyclerView这个组合,就会导致只有第一页RecyclerView能滑动,其余页的滑动事件全部被behavior处理掉了,也就是滑动全部成为了弹出隐藏你得弹出框,很显然,列表是需要先于弹出框消费滑动的,于是我们看看BottomSheetBehavior的源码,如下:

      @Nullable
      @VisibleForTesting
      View findScrollingChild(View view) {
        if (ViewCompat.isNestedScrollingEnabled(view)) {
          return view;
        }
        if (view instanceof ViewGroup) {
          ViewGroup group = (ViewGroup) view;
          for (int i = 0, count = group.getChildCount(); i < count; i++) {
            View scrollingChild = findScrollingChild(group.getChildAt(i));
            if (scrollingChild != null) {
              return scrollingChild;
            }
          }
        }
        return null;
      }
    

    关键的一段代码,在寻找滑动View的时候,对ViewGroup子View进行了遍历,再递归的寻找子View下可滑动的控件,当找到第一个可滑动控件时,将其返回,作为消费滑动事件的控件。
    这里就明白了只有第一个RecyclerView能滑动的原因:无论findScrollingChild什么时机被触发,永远都只会返回ViewPager的第一页中的RecyclerView。知道了原因,修改就很简单了:将ViewPager拿出来,单独进行一波单独遍历。那么我们新建一个MyViewPagerBottomSheetBehavior.java,将BottomSheetBehavior代码拷贝,改造findScrollingChild后如下:

        @Nullable
        @VisibleForTesting
        View findScrollingChild(View view) {
            if (ViewCompat.isNestedScrollingEnabled(view)) {
                return view;
            }
            if (view instanceof ViewPager) {
                ViewPager viewPager = (ViewPager) view;
                View currentViewPagerChild = ViewPagerUtils.getCurrentView(viewPager); //通过ViewPagerUtils找到当前在界面上的页面
                View scrollingChild = findScrollingChild(currentViewPagerChild);
                if (scrollingChild != null) {
                    return scrollingChild;
                }
                return currentViewPagerChild;
            }
    
            if (view instanceof ViewGroup) {
                ViewGroup group = (ViewGroup) view;
                for (int i = 0, count = group.getChildCount(); i < count; i++) {
                    View scrollingChild = findScrollingChild(group.getChildAt(i));
                    if (scrollingChild != null) {
                        return scrollingChild;
                    }
                }
            }
            return null;
        }
    

    第一个问题解决了~

    问题2:滑动事件全部被 RecyclerView 消费掉,滑动弹出和关闭功能消失了

    导致这个问题的原因也很简单,我们的滑动事件在 RecyclerView 加入前,都是由 BottomSheetBehavior 来消费的,当我们加入 RecyclerView 这种可滑动控件后,滑动事件都被其消费,这与 ViewPager 无关。

    多数情况下我们需要 RecyclerView 消费事件(滑动),但我们同时希望当 RecyclerView 滑动到顶部时,将事件又重新交给 Behavior 消费,这样就可以做到,列表在顶部时滑动开启/关闭弹出框
    实现需要分为以下几步

    • Behavior 添加 Flag ,标记列表是否滑动到最顶端
      由于需要适配ViewPager多页情况,一个Flag不能解决,需要一个HashMap,将页数与Flag关联起来
        //针对viewpager联动
        boolean isFirstFind = true; //第一次寻找联动View,为viewPager添加滑动监听
        private HashMap<Integer, Boolean> isScrollViewOnTopMap = new HashMap<>(); //关联页数与能否滑动flag的Map
        private View globalView;//保存下来的view,方便刷新寻找滑动控件时使用
        private int currentPagePosition = 0; //当前ViewPager的页数
        @Nullable
        @VisibleForTesting
        View findScrollingChild(View view) {
         ..........................
         }
    
    • 定义一个刷新滑动控件的方法,仅供 MyViewPagerBehavior 内部使用
        private void notifyScrollView() {
            nestedScrollingChildRef = new WeakReference<>(findScrollingChild(globalView));
        }
    
    • MyViewPagerBehavior内对外暴露一个设置Flag的方法
        public void setCurrentScrollViewOnTop(boolean scrollViewOnTop) {
            isScrollViewOnTopMap.put(currentPagePosition, scrollViewOnTop);
            Log.d("~~~~~~~~~",isScrollViewOnTopMap.toString());
            notifyScrollView();
        }
    
    • 更改 findScrollingChild(View view) 方法,适配列表在顶部时,滑动由 behavior 处理
        @Nullable
        @VisibleForTesting
        View findScrollingChild(View view) {
            if(view==null){
                return null;
            }
            globalView = view; //为全局view赋值
            boolean b = isScrollViewOnTopMap.getOrDefault(currentPagePosition,false );//列表是否处于顶部
            if (b) {//处于顶部,返回null,代表内部滑动控件不消费任何滑动事件,交由behavior处理
                return null;
            }
    
            if (ViewCompat.isNestedScrollingEnabled(view)) {
                return view;
            }
            if (view instanceof ViewPager) {
                ViewPager viewPager = (ViewPager) view;
                if (isFirstFind) {//初次寻找滑动view,添加viewpager翻页监听
                    for (int i = 0; i < Objects.requireNonNull(viewPager.getAdapter()).getCount(); i++) {
                        isScrollViewOnTopMap.put(i,true); //将所有顶部标记置为false,默认不在顶部
                    }
                    viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                        @Override
                        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                            //翻页后,将现在的页数更新
                            currentPagePosition = position;
                        }
    
                        @Override
                        public void onPageSelected(int position) {
    
                        }
    
                        @Override
                        public void onPageScrollStateChanged(int state) {
    
                        }
                    });
                    isFirstFind = false;
                }
                View currentViewPagerChild = ViewPagerUtils.getCurrentView(viewPager); //找到ViewPager当前的view
                View scrollingChild = findScrollingChild(currentViewPagerChild);
                if (scrollingChild != null) {
                    return scrollingChild;
                }
                return currentViewPagerChild;
            }
    
    • 监听 RecyclerView 滑动,在对顶部时,通知 Behavior
      这是在Fragment或者Activity中,你得behavior一定要自己去取,可能就在你得 Fragment/Activity 中通过MyViewPagerBehavior.from()就能获取,也可能你要从其他 ActivityFragment 注入进来,具体只有你自己了解;不要忘了:
      • behavior变量类型一定要写刚才创建的 MyViewPagerBottomSheetBehavior
      • xml中更换behavior为 MyViewPagerBottomSheetBehavior
            //上拉加载
            binding.rvList.addOnScrollListener(new RecyclerView.OnScrollListener() {
                @Override
                public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                    if (behavior != null) {
                        Log.d("!!!!!!!!!", String.valueOf(!recyclerView.canScrollVertically(-1)) + dy);
                        if (dy == 0) {
                            //指尖未真实上下滑动(针对从viewpager其他页面切换过来时),不做任何操作
                            return;
                        }
                        if (!recyclerView.canScrollVertically(-1) && dy < 0) {
                            //不可以下拉,并且手势是下拉,通知behavior已经列表已经在顶部了
                            Log.d("~~~~~", "划不动了");
                            behavior.setCurrentScrollViewOnTop(true);
                        } else {
                            //可以下拉或者手势不是下拉,通知behavior已经列表不在顶部
                            behavior.setCurrentScrollViewOnTop(false);
                        }
                    }
                }
            });
    

    贴上完整的MyViewPagerBottomSheetBehavior

    package com.xxx.wordingtech.ui.widget;
    
    import com.google.android.material.R;
    
    import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
    
    import android.animation.ValueAnimator;
    import android.animation.ValueAnimator.AnimatorUpdateListener;
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.content.res.ColorStateList;
    import android.content.res.TypedArray;
    import android.os.Build;
    import android.os.Build.VERSION;
    import android.os.Build.VERSION_CODES;
    import android.os.Parcel;
    import android.os.Parcelable;
    
    import androidx.annotation.FloatRange;
    import androidx.annotation.IntDef;
    import androidx.annotation.NonNull;
    import androidx.annotation.Nullable;
    import androidx.annotation.RestrictTo;
    import androidx.annotation.VisibleForTesting;
    import androidx.core.math.MathUtils;
    import androidx.core.view.ViewCompat;
    import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
    import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
    import androidx.core.view.accessibility.AccessibilityViewCommand;
    
    import android.util.AttributeSet;
    import android.util.Log;
    import android.util.Pair;
    import android.util.TypedValue;
    import android.view.MotionEvent;
    import android.view.VelocityTracker;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.view.ViewGroup;
    import android.view.ViewParent;
    import android.view.WindowInsets;
    
    import androidx.coordinatorlayout.widget.CoordinatorLayout;
    import androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams;
    import androidx.customview.view.AbsSavedState;
    import androidx.customview.widget.ViewDragHelper;
    import androidx.viewpager.widget.ViewPager;
    import androidx.viewpager.widget.ViewPagerUtils;
    
    import com.google.android.material.bottomsheet.BottomSheetBehavior;
    import com.google.android.material.resources.MaterialResources;
    import com.google.android.material.shape.MaterialShapeDrawable;
    import com.google.android.material.shape.ShapeAppearanceModel;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.ref.WeakReference;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Objects;
    
    
    public class MyViewPagerBottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
    
        /**
         * Callback for monitoring events about bottom sheets.
         */
        public abstract static class BottomSheetCallback {
    
            /**
             * Called when the bottom sheet changes its state.
             *
             * @param bottomSheet The bottom sheet view.
             * @param newState    The new state. This will be one of {@link #STATE_DRAGGING}, {@link
             *                    #STATE_SETTLING}, {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link
             *                    #STATE_HIDDEN}, or {@link #STATE_HALF_EXPANDED}.
             */
            public abstract void onStateChanged(@NonNull View bottomSheet, @State int newState);
    
            /**
             * Called when the bottom sheet is being dragged.
             *
             * @param bottomSheet The bottom sheet view.
             * @param slideOffset The new offset of this bottom sheet within [-1,1] range. Offset increases
             *                    as this bottom sheet is moving upward. From 0 to 1 the sheet is between collapsed and
             *                    expanded states and from -1 to 0 it is between hidden and collapsed states.
             */
            public abstract void onSlide(@NonNull View bottomSheet, float slideOffset);
        }
    
        /**
         * The bottom sheet is dragging.
         */
        public static final int STATE_DRAGGING = 1;
    
        /**
         * The bottom sheet is settling.
         */
        public static final int STATE_SETTLING = 2;
    
        /**
         * The bottom sheet is expanded.
         */
        public static final int STATE_EXPANDED = 3;
    
        /**
         * The bottom sheet is collapsed.
         */
        public static final int STATE_COLLAPSED = 4;
    
        /**
         * The bottom sheet is hidden.
         */
        public static final int STATE_HIDDEN = 5;
    
        /**
         * The bottom sheet is half-expanded (used when mFitToContents is false).
         */
        public static final int STATE_HALF_EXPANDED = 6;
    
        /**
         * @hide
         */
        @RestrictTo(LIBRARY_GROUP)
        @IntDef({
                STATE_EXPANDED,
                STATE_COLLAPSED,
                STATE_DRAGGING,
                STATE_SETTLING,
                STATE_HIDDEN,
                STATE_HALF_EXPANDED
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface State {
        }
    
        /**
         * Peek at the 16:9 ratio keyline of its parent.
         *
         * <p>This can be used as a parameter for {@link #setPeekHeight(int)}. {@link #getPeekHeight()}
         * will return this when the value is set.
         */
        public static final int PEEK_HEIGHT_AUTO = -1;
    
        /**
         * This flag will preserve the peekHeight int value on configuration change.
         */
        public static final int SAVE_PEEK_HEIGHT = 0x1;
    
        /**
         * This flag will preserve the fitToContents boolean value on configuration change.
         */
        public static final int SAVE_FIT_TO_CONTENTS = 1 << 1;
    
        /**
         * This flag will preserve the hideable boolean value on configuration change.
         */
        public static final int SAVE_HIDEABLE = 1 << 2;
    
        /**
         * This flag will preserve the skipCollapsed boolean value on configuration change.
         */
        public static final int SAVE_SKIP_COLLAPSED = 1 << 3;
    
        /**
         * This flag will preserve all aforementioned values on configuration change.
         */
        public static final int SAVE_ALL = -1;
    
        /**
         * This flag will not preserve the aforementioned values set at runtime if the view is destroyed
         * and recreated. The only value preserved will be the positional state, e.g. collapsed, hidden,
         * expanded, etc. This is the default behavior.
         */
        public static final int SAVE_NONE = 0;
    
        /**
         * @hide
         */
        @RestrictTo(LIBRARY_GROUP)
        @IntDef(
                flag = true,
                value = {
                        SAVE_PEEK_HEIGHT,
                        SAVE_FIT_TO_CONTENTS,
                        SAVE_HIDEABLE,
                        SAVE_SKIP_COLLAPSED,
                        SAVE_ALL,
                        SAVE_NONE,
                })
        @Retention(RetentionPolicy.SOURCE)
        public @interface SaveFlags {
        }
    
        private static final String TAG = "BottomSheetBehavior";
    
        @SaveFlags
        private int saveFlags = SAVE_NONE;
    
        private static final int SIGNIFICANT_VEL_THRESHOLD = 500;
    
        private static final float HIDE_THRESHOLD = 0.5f;
    
        private static final float HIDE_FRICTION = 0.1f;
    
        private static final int CORNER_ANIMATION_DURATION = 500;
    
        private boolean fitToContents = true;
    
        private boolean updateImportantForAccessibilityOnSiblings = false;
    
        private float maximumVelocity;
    
        /**
         * Peek height set by the user.
         */
        private int peekHeight;
    
        /**
         * Whether or not to use automatic peek height.
         */
        private boolean peekHeightAuto;
    
        /**
         * Minimum peek height permitted.
         */
        private int peekHeightMin;
    
        /**
         * True if Behavior has a non-null value for the @shapeAppearance attribute
         */
        private boolean shapeThemingEnabled;
    
        private MaterialShapeDrawable materialShapeDrawable;
    
        private boolean gestureInsetBottomIgnored;
    
        /**
         * Default Shape Appearance to be used in bottomsheet
         */
        private ShapeAppearanceModel shapeAppearanceModelDefault;
    
        private boolean isShapeExpanded;
    
        private SettleRunnable settleRunnable = null;
    
        @Nullable
        private ValueAnimator interpolatorAnimator;
    
        private static final int DEF_STYLE_RES = R.style.Widget_Design_BottomSheet_Modal;
    
        int expandedOffset;
    
        int fitToContentsOffset;
    
        int halfExpandedOffset;
    
        float halfExpandedRatio = 0.5f;
    
        int collapsedOffset;
    
        float elevation = -1;
    
        boolean hideable;
    
        private boolean skipCollapsed;
    
        private boolean draggable = true;
    
        @State
        int state = STATE_COLLAPSED;
    
        @Nullable
        ViewDragHelper viewDragHelper;
    
        private boolean ignoreEvents;
    
        private int lastNestedScrollDy;
    
        private boolean nestedScrolled;
    
        int parentWidth;
        int parentHeight;
    
        @Nullable
        WeakReference<V> viewRef;
    
        @Nullable
        WeakReference<View> nestedScrollingChildRef;
    
        @NonNull
        private final ArrayList<BottomSheetCallback> callbacks = new ArrayList<>();
    
        @Nullable
        private VelocityTracker velocityTracker;
    
        int activePointerId;
    
        private int initialY;
    
        boolean touchingScrollingChild;
    
        @Nullable
        private Map<View, Integer> importantForAccessibilityMap;
    
        public MyViewPagerBottomSheetBehavior() {
        }
    
        public MyViewPagerBottomSheetBehavior(@NonNull Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BottomSheetBehavior_Layout);
            this.shapeThemingEnabled = a.hasValue(R.styleable.BottomSheetBehavior_Layout_shapeAppearance);
            boolean hasBackgroundTint = a.hasValue(R.styleable.BottomSheetBehavior_Layout_backgroundTint);
            if (hasBackgroundTint) {
                @SuppressLint("RestrictedApi") ColorStateList bottomSheetColor =
                        MaterialResources.getColorStateList(
                                context, a, R.styleable.BottomSheetBehavior_Layout_backgroundTint);
                createMaterialShapeDrawable(context, attrs, hasBackgroundTint, bottomSheetColor);
            } else {
                createMaterialShapeDrawable(context, attrs, hasBackgroundTint);
            }
            createShapeValueAnimator();
    
            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
                this.elevation = a.getDimension(R.styleable.BottomSheetBehavior_Layout_android_elevation, -1);
            }
    
            TypedValue value = a.peekValue(R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight);
            if (value != null && value.data == PEEK_HEIGHT_AUTO) {
                setPeekHeight(value.data);
            } else {
                setPeekHeight(
                        a.getDimensionPixelSize(
                                R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight, PEEK_HEIGHT_AUTO));
            }
            setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_hideable, false));
            setGestureInsetBottomIgnored(
                    a.getBoolean(R.styleable.BottomSheetBehavior_Layout_gestureInsetBottomIgnored, false));
            setFitToContents(
                    a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_fitToContents, true));
            setSkipCollapsed(
                    a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed, false));
            setDraggable(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_draggable, true));
            setSaveFlags(a.getInt(R.styleable.BottomSheetBehavior_Layout_behavior_saveFlags, SAVE_NONE));
            setHalfExpandedRatio(
                    a.getFloat(R.styleable.BottomSheetBehavior_Layout_behavior_halfExpandedRatio, 0.5f));
    
            value = a.peekValue(R.styleable.BottomSheetBehavior_Layout_behavior_expandedOffset);
            if (value != null && value.type == TypedValue.TYPE_FIRST_INT) {
                setExpandedOffset(value.data);
            } else {
                setExpandedOffset(
                        a.getDimensionPixelOffset(
                                R.styleable.BottomSheetBehavior_Layout_behavior_expandedOffset, 0));
            }
            a.recycle();
            ViewConfiguration configuration = ViewConfiguration.get(context);
            maximumVelocity = configuration.getScaledMaximumFlingVelocity();
        }
    
        @NonNull
        @Override
        public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child) {
            return new SavedState(super.onSaveInstanceState(parent, child), this);
        }
    
        @Override
        public void onRestoreInstanceState(
                @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull Parcelable state) {
            SavedState ss = (SavedState) state;
            super.onRestoreInstanceState(parent, child, ss.getSuperState());
            // Restore Optional State values designated by saveFlags
            restoreOptionalState(ss);
            // Intermediate states are restored as collapsed state
            if (ss.state == STATE_DRAGGING || ss.state == STATE_SETTLING) {
                this.state = STATE_COLLAPSED;
            } else {
                this.state = ss.state;
            }
        }
    
        @Override
        public void onAttachedToLayoutParams(@NonNull LayoutParams layoutParams) {
            super.onAttachedToLayoutParams(layoutParams);
            // These may already be null, but just be safe, explicitly assign them. This lets us know the
            // first time we layout with this behavior by checking (viewRef == null).
            viewRef = null;
            viewDragHelper = null;
        }
    
        @Override
        public void onDetachedFromLayoutParams() {
            super.onDetachedFromLayoutParams();
            // Release references so we don't run unnecessary codepaths while not attached to a view.
            viewRef = null;
            viewDragHelper = null;
        }
    
        @Override
        public boolean onLayoutChild(
                @NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
            if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
                child.setFitsSystemWindows(true);
            }
    
            if (viewRef == null) {
                // First layout with this behavior.
                peekHeightMin =
                        parent.getResources().getDimensionPixelSize(R.dimen.design_bottom_sheet_peek_height_min);
                setSystemGestureInsets(parent);
                viewRef = new WeakReference<>(child);
                // Only set MaterialShapeDrawable as background if shapeTheming is enabled, otherwise will
                // default to android:background declared in styles or layout.
                if (shapeThemingEnabled && materialShapeDrawable != null) {
                    ViewCompat.setBackground(child, materialShapeDrawable);
                }
                // Set elevation on MaterialShapeDrawable
                if (materialShapeDrawable != null) {
                    // Use elevation attr if set on bottomsheet; otherwise, use elevation of child view.
                    materialShapeDrawable.setElevation(
                            elevation == -1 ? ViewCompat.getElevation(child) : elevation);
                    // Update the material shape based on initial state.
                    isShapeExpanded = state == STATE_EXPANDED;
                    materialShapeDrawable.setInterpolation(isShapeExpanded ? 0f : 1f);
                }
                updateAccessibilityActions();
                if (ViewCompat.getImportantForAccessibility(child)
                        == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                    ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
                }
            }
            if (viewDragHelper == null) {
                viewDragHelper = ViewDragHelper.create(parent, dragCallback);
            }
    
            int savedTop = child.getTop();
            // First let the parent lay it out
            parent.onLayoutChild(child, layoutDirection);
            // Offset the bottom sheet
            parentWidth = parent.getWidth();
            parentHeight = parent.getHeight();
            fitToContentsOffset = Math.max(0, parentHeight - child.getHeight());
            calculateHalfExpandedOffset();
            calculateCollapsedOffset();
    
            if (state == STATE_EXPANDED) {
                ViewCompat.offsetTopAndBottom(child, getExpandedOffset());
            } else if (state == STATE_HALF_EXPANDED) {
                ViewCompat.offsetTopAndBottom(child, halfExpandedOffset);
            } else if (hideable && state == STATE_HIDDEN) {
                ViewCompat.offsetTopAndBottom(child, parentHeight);
            } else if (state == STATE_COLLAPSED) {
                ViewCompat.offsetTopAndBottom(child, collapsedOffset);
            } else if (state == STATE_DRAGGING || state == STATE_SETTLING) {
                ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
            }
    
            nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
            return true;
        }
    
        @Override
        public boolean onInterceptTouchEvent(
                @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) {
            if (!child.isShown() || !draggable) {
                ignoreEvents = true;
                return false;
            }
            int action = event.getActionMasked();
            // Record the velocity
            if (action == MotionEvent.ACTION_DOWN) {
                reset();
            }
            if (velocityTracker == null) {
                velocityTracker = VelocityTracker.obtain();
            }
            velocityTracker.addMovement(event);
            switch (action) {
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    touchingScrollingChild = false;
                    activePointerId = MotionEvent.INVALID_POINTER_ID;
                    // Reset the ignore flag
                    if (ignoreEvents) {
                        ignoreEvents = false;
                        return false;
                    }
                    break;
                case MotionEvent.ACTION_DOWN:
                    int initialX = (int) event.getX();
                    initialY = (int) event.getY();
                    // Only intercept nested scrolling events here if the view not being moved by the
                    // ViewDragHelper.
                    if (state != STATE_SETTLING) {
                        View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
                        if (scroll != null && parent.isPointInChildBounds(scroll, initialX, initialY)) {
                            activePointerId = event.getPointerId(event.getActionIndex());
                            touchingScrollingChild = true;
                        }
                    }
                    ignoreEvents =
                            activePointerId == MotionEvent.INVALID_POINTER_ID
                                    && !parent.isPointInChildBounds(child, initialX, initialY);
                    break;
                default: // fall out
            }
            if (!ignoreEvents
                    && viewDragHelper != null
                    && viewDragHelper.shouldInterceptTouchEvent(event)) {
                return true;
            }
            // We have to handle cases that the ViewDragHelper does not capture the bottom sheet because
            // it is not the top most view of its parent. This is not necessary when the touch event is
            // happening over the scrolling content as nested scrolling logic handles that case.
            View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
            return action == MotionEvent.ACTION_MOVE
                    && scroll != null
                    && !ignoreEvents
                    && state != STATE_DRAGGING
                    && !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY())
                    && viewDragHelper != null
                    && Math.abs(initialY - event.getY()) > viewDragHelper.getTouchSlop();
        }
    
        @Override
        public boolean onTouchEvent(
                @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) {
            if (!child.isShown()) {
                return false;
            }
            int action = event.getActionMasked();
            if (state == STATE_DRAGGING && action == MotionEvent.ACTION_DOWN) {
                return true;
            }
            if (viewDragHelper != null) {
                viewDragHelper.processTouchEvent(event);
            }
            // Record the velocity
            if (action == MotionEvent.ACTION_DOWN) {
                reset();
            }
            if (velocityTracker == null) {
                velocityTracker = VelocityTracker.obtain();
            }
            velocityTracker.addMovement(event);
            // The ViewDragHelper tries to capture only the top-most View. We have to explicitly tell it
            // to capture the bottom sheet in case it is not captured and the touch slop is passed.
            if (action == MotionEvent.ACTION_MOVE && !ignoreEvents) {
                if (Math.abs(initialY - event.getY()) > viewDragHelper.getTouchSlop()) {
                    viewDragHelper.captureChildView(child, event.getPointerId(event.getActionIndex()));
                }
            }
            return !ignoreEvents;
        }
    
        @Override
        public boolean onStartNestedScroll(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child,
                @NonNull View directTargetChild,
                @NonNull View target,
                int axes,
                int type) {
            lastNestedScrollDy = 0;
            nestedScrolled = false;
            return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
        }
    
        @Override
        public void onNestedPreScroll(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child,
                @NonNull View target,
                int dx,
                int dy,
                @NonNull int[] consumed,
                int type) {
            if (type == ViewCompat.TYPE_NON_TOUCH) {
                // Ignore fling here. The ViewDragHelper handles it.
                return;
            }
            View scrollingChild = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
            if (target != scrollingChild) {
                return;
            }
            int currentTop = child.getTop();
            int newTop = currentTop - dy;
            if (dy > 0) { // Upward
                if (newTop < getExpandedOffset()) {
                    consumed[1] = currentTop - getExpandedOffset();
                    ViewCompat.offsetTopAndBottom(child, -consumed[1]);
                    setStateInternal(STATE_EXPANDED);
                } else {
                    if (!draggable) {
                        // Prevent dragging
                        return;
                    }
    
                    consumed[1] = dy;
                    ViewCompat.offsetTopAndBottom(child, -dy);
                    setStateInternal(STATE_DRAGGING);
                }
            } else if (dy < 0) { // Downward
                if (!target.canScrollVertically(-1)) {
                    if (newTop <= collapsedOffset || hideable) {
                        if (!draggable) {
                            // Prevent dragging
                            return;
                        }
    
                        consumed[1] = dy;
                        ViewCompat.offsetTopAndBottom(child, -dy);
                        setStateInternal(STATE_DRAGGING);
                    } else {
                        consumed[1] = currentTop - collapsedOffset;
                        ViewCompat.offsetTopAndBottom(child, -consumed[1]);
                        setStateInternal(STATE_COLLAPSED);
                    }
                }
            }
            dispatchOnSlide(child.getTop());
            lastNestedScrollDy = dy;
            nestedScrolled = true;
        }
    
        @Override
        public void onStopNestedScroll(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child,
                @NonNull View target,
                int type) {
            if (child.getTop() == getExpandedOffset()) {
                setStateInternal(STATE_EXPANDED);
                return;
            }
            if (nestedScrollingChildRef == null
                    || target != nestedScrollingChildRef.get()
                    || !nestedScrolled) {
                return;
            }
            int top;
            int targetState;
            if (lastNestedScrollDy > 0) {
                if (fitToContents) {
                    top = fitToContentsOffset;
                    targetState = STATE_EXPANDED;
                } else {
                    int currentTop = child.getTop();
                    if (currentTop > halfExpandedOffset) {
                        top = halfExpandedOffset;
                        targetState = STATE_HALF_EXPANDED;
                    } else {
                        top = expandedOffset;
                        targetState = STATE_EXPANDED;
                    }
                }
            } else if (hideable && shouldHide(child, getYVelocity())) {
                top = parentHeight;
                targetState = STATE_HIDDEN;
            } else if (lastNestedScrollDy == 0) {
                int currentTop = child.getTop();
                if (fitToContents) {
                    if (Math.abs(currentTop - fitToContentsOffset) < Math.abs(currentTop - collapsedOffset)) {
                        top = fitToContentsOffset;
                        targetState = STATE_EXPANDED;
                    } else {
                        top = collapsedOffset;
                        targetState = STATE_COLLAPSED;
                    }
                } else {
                    if (currentTop < halfExpandedOffset) {
                        if (currentTop < Math.abs(currentTop - collapsedOffset)) {
                            top = expandedOffset;
                            targetState = STATE_EXPANDED;
                        } else {
                            top = halfExpandedOffset;
                            targetState = STATE_HALF_EXPANDED;
                        }
                    } else {
                        if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) {
                            top = halfExpandedOffset;
                            targetState = STATE_HALF_EXPANDED;
                        } else {
                            top = collapsedOffset;
                            targetState = STATE_COLLAPSED;
                        }
                    }
                }
            } else {
                if (fitToContents) {
                    top = collapsedOffset;
                    targetState = STATE_COLLAPSED;
                } else {
                    // Settle to nearest height.
                    int currentTop = child.getTop();
                    if (Math.abs(currentTop - halfExpandedOffset) < Math.abs(currentTop - collapsedOffset)) {
                        top = halfExpandedOffset;
                        targetState = STATE_HALF_EXPANDED;
                    } else {
                        top = collapsedOffset;
                        targetState = STATE_COLLAPSED;
                    }
                }
            }
            startSettlingAnimation(child, targetState, top, false);
            nestedScrolled = false;
        }
    
        @Override
        public void onNestedScroll(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child,
                @NonNull View target,
                int dxConsumed,
                int dyConsumed,
                int dxUnconsumed,
                int dyUnconsumed,
                int type,
                @NonNull int[] consumed) {
            // Overridden to prevent the default consumption of the entire scroll distance.
        }
    
        @Override
        public boolean onNestedPreFling(
                @NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child,
                @NonNull View target,
                float velocityX,
                float velocityY) {
            if (nestedScrollingChildRef != null) {
                return target == nestedScrollingChildRef.get()
                        && (state != STATE_EXPANDED
                        || super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY));
            } else {
                return false;
            }
        }
    
        /**
         * @return whether the height of the expanded sheet is determined by the height of its contents,
         * or if it is expanded in two stages (half the height of the parent container, full height of
         * parent container).
         */
        public boolean isFitToContents() {
            return fitToContents;
        }
    
        /**
         * Sets whether the height of the expanded sheet is determined by the height of its contents, or
         * if it is expanded in two stages (half the height of the parent container, full height of parent
         * container). Default value is true.
         *
         * @param fitToContents whether or not to fit the expanded sheet to its contents.
         */
        public void setFitToContents(boolean fitToContents) {
            if (this.fitToContents == fitToContents) {
                return;
            }
            this.fitToContents = fitToContents;
    
            // If sheet is already laid out, recalculate the collapsed offset based on new setting.
            // Otherwise, let onLayoutChild handle this later.
            if (viewRef != null) {
                calculateCollapsedOffset();
            }
            // Fix incorrect expanded settings depending on whether or not we are fitting sheet to contents.
            setStateInternal((this.fitToContents && state == STATE_HALF_EXPANDED) ? STATE_EXPANDED : state);
    
            updateAccessibilityActions();
        }
    
        /**
         * Sets the height of the bottom sheet when it is collapsed.
         *
         * @param peekHeight The height of the collapsed bottom sheet in pixels, or {@link
         *                   #PEEK_HEIGHT_AUTO} to configure the sheet to peek automatically at 16:9 ratio keyline.
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_peekHeight
         */
        public void setPeekHeight(int peekHeight) {
            setPeekHeight(peekHeight, false);
        }
    
        /**
         * Sets the height of the bottom sheet when it is collapsed while optionally animating between the
         * old height and the new height.
         *
         * @param peekHeight The height of the collapsed bottom sheet in pixels, or {@link
         *                   #PEEK_HEIGHT_AUTO} to configure the sheet to peek automatically at 16:9 ratio keyline.
         * @param animate    Whether to animate between the old height and the new height.
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_peekHeight
         */
        public final void setPeekHeight(int peekHeight, boolean animate) {
            boolean layout = false;
            if (peekHeight == PEEK_HEIGHT_AUTO) {
                if (!peekHeightAuto) {
                    peekHeightAuto = true;
                    layout = true;
                }
            } else if (peekHeightAuto || this.peekHeight != peekHeight) {
                peekHeightAuto = false;
                this.peekHeight = Math.max(0, peekHeight);
                layout = true;
            }
            // If sheet is already laid out, recalculate the collapsed offset based on new setting.
            // Otherwise, let onLayoutChild handle this later.
            if (layout && viewRef != null) {
                calculateCollapsedOffset();
                if (state == STATE_COLLAPSED) {
                    V view = viewRef.get();
                    if (view != null) {
                        if (animate) {
                            settleToStatePendingLayout(state);
                        } else {
                            view.requestLayout();
                        }
                    }
                }
            }
        }
    
        /**
         * Gets the height of the bottom sheet when it is collapsed.
         *
         * @return The height of the collapsed bottom sheet in pixels, or {@link #PEEK_HEIGHT_AUTO} if the
         * sheet is configured to peek automatically at 16:9 ratio keyline
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_peekHeight
         */
        public int getPeekHeight() {
            return peekHeightAuto ? PEEK_HEIGHT_AUTO : peekHeight;
        }
    
        /**
         * Determines the height of the BottomSheet in the {@link #STATE_HALF_EXPANDED} state. The
         * material guidelines recommended a value of 0.5, which results in the sheet filling half of the
         * parent. The height of the BottomSheet will be smaller as this ratio is decreased and taller as
         * it is increased. The default value is 0.5.
         *
         * @param ratio a float between 0 and 1, representing the {@link #STATE_HALF_EXPANDED} ratio.
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_halfExpandedRatio
         */
        public void setHalfExpandedRatio(@FloatRange(from = 0.0f, to = 1.0f) float ratio) {
    
            if ((ratio <= 0) || (ratio >= 1)) {
                throw new IllegalArgumentException("ratio must be a float value between 0 and 1");
            }
            this.halfExpandedRatio = ratio;
            // If sheet is already laid out, recalculate the half expanded offset based on new setting.
            // Otherwise, let onLayoutChild handle this later.
            if (viewRef != null) {
                calculateHalfExpandedOffset();
            }
        }
    
        /**
         * Gets the ratio for the height of the BottomSheet in the {@link #STATE_HALF_EXPANDED} state.
         *
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_halfExpandedRatio
         */
        @FloatRange(from = 0.0f, to = 1.0f)
        public float getHalfExpandedRatio() {
            return halfExpandedRatio;
        }
    
        /**
         * Determines the top offset of the BottomSheet in the {@link #STATE_EXPANDED} state when
         * fitsToContent is false. The default value is 0, which results in the sheet matching the
         * parent's top.
         *
         * @param offset an integer value greater than equal to 0, representing the {@link
         *               #STATE_EXPANDED} offset. Value must not exceed the offset in the half expanded state.
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_expandedOffset
         */
        public void setExpandedOffset(int offset) {
            if (offset < 0) {
                throw new IllegalArgumentException("offset must be greater than or equal to 0");
            }
            this.expandedOffset = offset;
        }
    
        /**
         * Returns the current expanded offset. If {@code fitToContents} is true, it will automatically
         * pick the offset depending on the height of the content.
         *
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_expandedOffset
         */
        public int getExpandedOffset() {
            return fitToContents ? fitToContentsOffset : expandedOffset;
        }
    
        /**
         * Sets whether this bottom sheet can hide when it is swiped down.
         *
         * @param hideable {@code true} to make this bottom sheet hideable.
         * @attr ref com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_hideable
         */
        public void setHideable(boolean hideable) {
            if (this.hideable != hideable) {
                this.hideable = hideable;
                if (!hideable && state == STATE_HIDDEN) {
                    // Lift up to collapsed state
                    setState(STATE_COLLAPSED);
                }
                updateAccessibilityActions();
            }
        }
    
        /**
         * Gets whether this bottom sheet can hide when it is swiped down.
         *
         * @return {@code true} if this bottom sheet can hide.
         * @attr ref com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_hideable
         */
        public boolean isHideable() {
            return hideable;
        }
    
        /**
         * Sets whether this bottom sheet should skip the collapsed state when it is being hidden after it
         * is expanded once. Setting this to true has no effect unless the sheet is hideable.
         *
         * @param skipCollapsed True if the bottom sheet should skip the collapsed state.
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_skipCollapsed
         */
        public void setSkipCollapsed(boolean skipCollapsed) {
            this.skipCollapsed = skipCollapsed;
        }
    
        /**
         * Sets whether this bottom sheet should skip the collapsed state when it is being hidden after it
         * is expanded once.
         *
         * @return Whether the bottom sheet should skip the collapsed state.
         * @attr ref
         * com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_skipCollapsed
         */
        public boolean getSkipCollapsed() {
            return skipCollapsed;
        }
    
        /**
         * Sets whether this bottom sheet is can be collapsed/expanded by dragging. Note: When disabling
         * dragging, an app will require to implement a custom way to expand/collapse the bottom sheet
         *
         * @param draggable {@code false} to prevent dragging the sheet to collapse and expand
         * @attr ref com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_draggable
         */
        public void setDraggable(boolean draggable) {
            this.draggable = draggable;
        }
    
        public boolean isDraggable() {
            return draggable;
        }
    
        /**
         * Sets save flags to be preserved in bottomsheet on configuration change.
         *
         * @param flags bitwise int of {@link #SAVE_PEEK_HEIGHT}, {@link #SAVE_FIT_TO_CONTENTS}, {@link
         *              #SAVE_HIDEABLE}, {@link #SAVE_SKIP_COLLAPSED}, {@link #SAVE_ALL} and {@link #SAVE_NONE}.
         * @attr ref com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_saveFlags
         * @see #getSaveFlags()
         */
        public void setSaveFlags(@SaveFlags int flags) {
            this.saveFlags = flags;
        }
    
        /**
         * Returns the save flags.
         *
         * @attr ref com.google.android.material.R.styleable#BottomSheetBehavior_Layout_behavior_saveFlags
         * @see #setSaveFlags(int)
         */
        @SaveFlags
        public int getSaveFlags() {
            return this.saveFlags;
        }
    
        /**
         * Sets a callback to be notified of bottom sheet events.
         *
         * @param callback The callback to notify when bottom sheet events occur.
         * @deprecated use {@link #addBottomSheetCallback(BottomSheetCallback)} and {@link
         * #removeBottomSheetCallback(BottomSheetCallback)} instead
         */
        @Deprecated
        public void setBottomSheetCallback(BottomSheetCallback callback) {
            Log.w(
                    TAG,
                    "BottomSheetBehavior now supports multiple callbacks. `setBottomSheetCallback()` removes"
                            + " all existing callbacks, including ones set internally by library authors, which"
                            + " may result in unintended behavior. This may change in the future. Please use"
                            + " `addBottomSheetCallback()` and `removeBottomSheetCallback()` instead to set your"
                            + " own callbacks.");
            callbacks.clear();
            if (callback != null) {
                callbacks.add(callback);
            }
        }
    
        /**
         * Adds a callback to be notified of bottom sheet events.
         *
         * @param callback The callback to notify when bottom sheet events occur.
         */
        public void addBottomSheetCallback(@NonNull BottomSheetCallback callback) {
            if (!callbacks.contains(callback)) {
                callbacks.add(callback);
            }
        }
    
        /**
         * Removes a previously added callback.
         *
         * @param callback The callback to remove.
         */
        public void removeBottomSheetCallback(@NonNull BottomSheetCallback callback) {
            callbacks.remove(callback);
        }
    
        /**
         * Sets the state of the bottom sheet. The bottom sheet will transition to that state with
         * animation.
         *
         * @param state One of {@link #STATE_COLLAPSED}, {@link #STATE_EXPANDED}, {@link #STATE_HIDDEN},
         *              or {@link #STATE_HALF_EXPANDED}.
         */
        public void setState(@State int state) {
            if (state == this.state) {
                return;
            }
            if (viewRef == null) {
                // The view is not laid out yet; modify mState and let onLayoutChild handle it later
                if (state == STATE_COLLAPSED
                        || state == STATE_EXPANDED
                        || state == STATE_HALF_EXPANDED
                        || (hideable && state == STATE_HIDDEN)) {
                    this.state = state;
                }
                return;
            }
            settleToStatePendingLayout(state);
        }
    
        /**
         * Sets whether this bottom sheet should adjust it's position based on the system gesture area on
         * Android Q and above.
         *
         * <p>Note: the bottom sheet will only adjust it's position if it would be unable to be scrolled
         * upwards because the peekHeight is less than the gesture inset margins,(because that would cause
         * a gesture conflict), gesture navigation is enabled, and this {@code ignoreGestureInsetBottom}
         * flag is false.
         */
        public void setGestureInsetBottomIgnored(boolean gestureInsetBottomIgnored) {
            this.gestureInsetBottomIgnored = gestureInsetBottomIgnored;
        }
    
        /**
         * Returns whether this bottom sheet should adjust it's position based on the system gesture area.
         */
        public boolean isGestureInsetBottomIgnored() {
            return gestureInsetBottomIgnored;
        }
    
        private void settleToStatePendingLayout(@State int state) {
            final V child = viewRef.get();
            if (child == null) {
                return;
            }
            // Start the animation; wait until a pending layout if there is one.
            ViewParent parent = child.getParent();
            if (parent != null && parent.isLayoutRequested() && ViewCompat.isAttachedToWindow(child)) {
                final int finalState = state;
                child.post(
                        new Runnable() {
                            @Override
                            public void run() {
                                settleToState(child, finalState);
                            }
                        });
            } else {
                settleToState(child, state);
            }
        }
    
        /**
         * Gets the current state of the bottom sheet.
         *
         * @return One of {@link #STATE_EXPANDED}, {@link #STATE_HALF_EXPANDED}, {@link #STATE_COLLAPSED},
         * {@link #STATE_DRAGGING}, {@link #STATE_SETTLING}, or {@link #STATE_HALF_EXPANDED}.
         */
        @State
        public int getState() {
            return state;
        }
    
        void setStateInternal(@State int state) {
            if (this.state == state) {
                return;
            }
            this.state = state;
    
            if (viewRef == null) {
                return;
            }
    
            View bottomSheet = viewRef.get();
            if (bottomSheet == null) {
                return;
            }
    
            if (state == STATE_EXPANDED) {
                updateImportantForAccessibility(true);
            } else if (state == STATE_HALF_EXPANDED || state == STATE_HIDDEN || state == STATE_COLLAPSED) {
                updateImportantForAccessibility(false);
            }
    
            updateDrawableForTargetState(state);
            for (int i = 0; i < callbacks.size(); i++) {
                callbacks.get(i).onStateChanged(bottomSheet, state);
            }
            updateAccessibilityActions();
        }
    
        private void updateDrawableForTargetState(@State int state) {
            if (state == STATE_SETTLING) {
                // Special case: we want to know which state we're settling to, so wait for another call.
                return;
            }
    
            boolean expand = state == STATE_EXPANDED;
            if (isShapeExpanded != expand) {
                isShapeExpanded = expand;
                if (materialShapeDrawable != null && interpolatorAnimator != null) {
                    if (interpolatorAnimator.isRunning()) {
                        interpolatorAnimator.reverse();
                    } else {
                        float to = expand ? 0f : 1f;
                        float from = 1f - to;
                        interpolatorAnimator.setFloatValues(from, to);
                        interpolatorAnimator.start();
                    }
                }
            }
        }
    
        private int calculatePeekHeight() {
            if (peekHeightAuto) {
                return Math.max(peekHeightMin, parentHeight - parentWidth * 9 / 16);
            }
            return peekHeight;
        }
    
        private void calculateCollapsedOffset() {
            int peek = calculatePeekHeight();
    
            if (fitToContents) {
                collapsedOffset = Math.max(parentHeight - peek, fitToContentsOffset);
            } else {
                collapsedOffset = parentHeight - peek;
            }
        }
    
        private void calculateHalfExpandedOffset() {
            this.halfExpandedOffset = (int) (parentHeight * (1 - halfExpandedRatio));
        }
    
        private void reset() {
            activePointerId = ViewDragHelper.INVALID_POINTER;
            if (velocityTracker != null) {
                velocityTracker.recycle();
                velocityTracker = null;
            }
        }
    
        private void restoreOptionalState(@NonNull SavedState ss) {
            if (this.saveFlags == SAVE_NONE) {
                return;
            }
            if (this.saveFlags == SAVE_ALL || (this.saveFlags & SAVE_PEEK_HEIGHT) == SAVE_PEEK_HEIGHT) {
                this.peekHeight = ss.peekHeight;
            }
            if (this.saveFlags == SAVE_ALL
                    || (this.saveFlags & SAVE_FIT_TO_CONTENTS) == SAVE_FIT_TO_CONTENTS) {
                this.fitToContents = ss.fitToContents;
            }
            if (this.saveFlags == SAVE_ALL || (this.saveFlags & SAVE_HIDEABLE) == SAVE_HIDEABLE) {
                this.hideable = ss.hideable;
            }
            if (this.saveFlags == SAVE_ALL
                    || (this.saveFlags & SAVE_SKIP_COLLAPSED) == SAVE_SKIP_COLLAPSED) {
                this.skipCollapsed = ss.skipCollapsed;
            }
        }
    
        boolean shouldHide(@NonNull View child, float yvel) {
            if (skipCollapsed) {
                return true;
            }
            if (child.getTop() < collapsedOffset) {
                // It should not hide, but collapse.
                return false;
            }
            int peek = calculatePeekHeight();
            final float newTop = child.getTop() + yvel * HIDE_FRICTION;
            return Math.abs(newTop - collapsedOffset) / (float) peek > HIDE_THRESHOLD;
        }
    
        //针对viewpager联动
        boolean isFirstFind = true; //第一次寻找联动View,为viewPager添加滑动监听
        private HashMap<Integer, Boolean> isScrollViewOnTopMap = new HashMap<>(); //关联页数与能否滑动flag的Map
        private int currentPagePosition = 0; //当前ViewPager的页数
    
        public void setCurrentScrollViewOnTop(boolean scrollViewOnTop) {
            isScrollViewOnTopMap.put(currentPagePosition, scrollViewOnTop);
            Log.d("~~~~~~~~~",isScrollViewOnTopMap.toString());
            notifyScrollView();
        }
    
        private View globalView;
    
        private void notifyScrollView() {
            nestedScrollingChildRef = new WeakReference<>(findScrollingChild(globalView));
        }
    
        @Nullable
        @VisibleForTesting
        View findScrollingChild(View view) {
            if(view==null){
                return null;
            }
            globalView = view;
            boolean b = isScrollViewOnTopMap.getOrDefault(currentPagePosition,false );
            if (b) {
                return null;
            }
    
            if (ViewCompat.isNestedScrollingEnabled(view)) {
                return view;
            }
            if (view instanceof ViewPager) {
                ViewPager viewPager = (ViewPager) view;
                if (isFirstFind) {
                    for (int i = 0; i < Objects.requireNonNull(viewPager.getAdapter()).getCount(); i++) {
                        isScrollViewOnTopMap.put(i,true);
                    }
                    viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
                        @Override
                        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                            currentPagePosition = position;
                        }
    
                        @Override
                        public void onPageSelected(int position) {
    
                        }
    
                        @Override
                        public void onPageScrollStateChanged(int state) {
    
                        }
                    });
                    isFirstFind = false;
                }
                View currentViewPagerChild = ViewPagerUtils.getCurrentView(viewPager);
                View scrollingChild = findScrollingChild(currentViewPagerChild);
                if (scrollingChild != null) {
                    return scrollingChild;
                }
                return currentViewPagerChild;
            }
    
            if (view instanceof ViewGroup) {
                ViewGroup group = (ViewGroup) view;
                for (int i = 0, count = group.getChildCount(); i < count; i++) {
                    View scrollingChild = findScrollingChild(group.getChildAt(i));
                    if (scrollingChild != null) {
                        return scrollingChild;
                    }
                }
            }
            return null;
        }
    
        private void createMaterialShapeDrawable(
                @NonNull Context context, AttributeSet attrs, boolean hasBackgroundTint) {
            this.createMaterialShapeDrawable(context, attrs, hasBackgroundTint, null);
        }
    
        private void createMaterialShapeDrawable(
                @NonNull Context context,
                AttributeSet attrs,
                boolean hasBackgroundTint,
                @Nullable ColorStateList bottomSheetColor) {
            if (this.shapeThemingEnabled) {
                this.shapeAppearanceModelDefault =
                        ShapeAppearanceModel.builder(context, attrs, R.attr.bottomSheetStyle, DEF_STYLE_RES)
                                .build();
    
                this.materialShapeDrawable = new MaterialShapeDrawable(shapeAppearanceModelDefault);
                this.materialShapeDrawable.initializeElevationOverlay(context);
    
                if (hasBackgroundTint && bottomSheetColor != null) {
                    materialShapeDrawable.setFillColor(bottomSheetColor);
                } else {
                    // If the tint isn't set, use the theme default background color.
                    TypedValue defaultColor = new TypedValue();
                    context.getTheme().resolveAttribute(android.R.attr.colorBackground, defaultColor, true);
                    materialShapeDrawable.setTint(defaultColor.data);
                }
            }
        }
    
        private void createShapeValueAnimator() {
            interpolatorAnimator = ValueAnimator.ofFloat(0f, 1f);
            interpolatorAnimator.setDuration(CORNER_ANIMATION_DURATION);
            interpolatorAnimator.addUpdateListener(
                    new AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(@NonNull ValueAnimator animation) {
                            float value = (float) animation.getAnimatedValue();
                            if (materialShapeDrawable != null) {
                                materialShapeDrawable.setInterpolation(value);
                            }
                        }
                    });
        }
    
        private void setSystemGestureInsets(@NonNull CoordinatorLayout parent) {
            if (VERSION.SDK_INT >= VERSION_CODES.Q && !isGestureInsetBottomIgnored()) {
                WindowInsets windowInsets = parent.getRootWindowInsets();
                if (windowInsets != null) {
                    int systemMandatoryInsetsBottom = windowInsets.getSystemGestureInsets().bottom;
                    peekHeight += systemMandatoryInsetsBottom;
                }
            }
        }
    
        private float getYVelocity() {
            if (velocityTracker == null) {
                return 0;
            }
            velocityTracker.computeCurrentVelocity(1000, maximumVelocity);
            return velocityTracker.getYVelocity(activePointerId);
        }
    
        void settleToState(@NonNull View child, int state) {
            int top;
            if (state == STATE_COLLAPSED) {
                top = collapsedOffset;
            } else if (state == STATE_HALF_EXPANDED) {
                top = halfExpandedOffset;
                if (fitToContents && top <= fitToContentsOffset) {
                    // Skip to the expanded state if we would scroll past the height of the contents.
                    state = STATE_EXPANDED;
                    top = fitToContentsOffset;
                }
            } else if (state == STATE_EXPANDED) {
                top = getExpandedOffset();
            } else if (hideable && state == STATE_HIDDEN) {
                top = parentHeight;
            } else {
                throw new IllegalArgumentException("Illegal state argument: " + state);
            }
            startSettlingAnimation(child, state, top, false);
        }
    
        void startSettlingAnimation(View child, int state, int top, boolean settleFromViewDragHelper) {
            boolean startedSettling =
                    settleFromViewDragHelper
                            ? viewDragHelper.settleCapturedViewAt(child.getLeft(), top)
                            : viewDragHelper.smoothSlideViewTo(child, child.getLeft(), top);
            if (startedSettling) {
                setStateInternal(STATE_SETTLING);
                // STATE_SETTLING won't animate the material shape, so do that here with the target state.
                updateDrawableForTargetState(state);
                if (settleRunnable == null) {
                    // If the singleton SettleRunnable instance has not been instantiated, create it.
                    settleRunnable = new SettleRunnable(child, state);
                }
                // If the SettleRunnable has not been posted, post it with the correct state.
                if (settleRunnable.isPosted == false) {
                    settleRunnable.targetState = state;
                    ViewCompat.postOnAnimation(child, settleRunnable);
                    settleRunnable.isPosted = true;
                } else {
                    // Otherwise, if it has been posted, just update the target state.
                    settleRunnable.targetState = state;
                }
            } else {
                setStateInternal(state);
            }
        }
    
        private final ViewDragHelper.Callback dragCallback =
                new ViewDragHelper.Callback() {
    
                    @Override
                    public boolean tryCaptureView(@NonNull View child, int pointerId) {
                        if (state == STATE_DRAGGING) {
                            return false;
                        }
                        if (touchingScrollingChild) {
                            return false;
                        }
                        if (state == STATE_EXPANDED && activePointerId == pointerId) {
                            View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null;
                            if (scroll != null && scroll.canScrollVertically(-1)) {
                                // Let the content scroll up
                                return false;
                            }
                        }
                        return viewRef != null && viewRef.get() == child;
                    }
    
                    @Override
                    public void onViewPositionChanged(
                            @NonNull View changedView, int left, int top, int dx, int dy) {
                        dispatchOnSlide(top);
                    }
    
                    @Override
                    public void onViewDragStateChanged(int state) {
                        if (state == ViewDragHelper.STATE_DRAGGING && draggable) {
                            setStateInternal(STATE_DRAGGING);
                        }
                    }
    
                    private boolean releasedLow(@NonNull View child) {
                        // Needs to be at least half way to the bottom.
                        return child.getTop() > (parentHeight + getExpandedOffset()) / 2;
                    }
    
                    @Override
                    public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
                        int top;
                        @State int targetState;
                        if (yvel < 0) { // Moving up
                            if (fitToContents) {
                                top = fitToContentsOffset;
                                targetState = STATE_EXPANDED;
                            } else {
                                int currentTop = releasedChild.getTop();
                                if (currentTop > halfExpandedOffset) {
                                    top = halfExpandedOffset;
                                    targetState = STATE_HALF_EXPANDED;
                                } else {
                                    top = expandedOffset;
                                    targetState = STATE_EXPANDED;
                                }
                            }
                        } else if (hideable && shouldHide(releasedChild, yvel)) {
                            // Hide if the view was either released low or it was a significant vertical swipe
                            // otherwise settle to closest expanded state.
                            if ((Math.abs(xvel) < Math.abs(yvel) && yvel > SIGNIFICANT_VEL_THRESHOLD)
                                    || releasedLow(releasedChild)) {
                                top = parentHeight;
                                targetState = STATE_HIDDEN;
                            } else if (fitToContents) {
                                top = fitToContentsOffset;
                                targetState = STATE_EXPANDED;
                            } else if (Math.abs(releasedChild.getTop() - expandedOffset)
                                    < Math.abs(releasedChild.getTop() - halfExpandedOffset)) {
                                top = expandedOffset;
                                targetState = STATE_EXPANDED;
                            } else {
                                top = halfExpandedOffset;
                                targetState = STATE_HALF_EXPANDED;
                            }
                        } else if (yvel == 0.f || Math.abs(xvel) > Math.abs(yvel)) {
                            // If the Y velocity is 0 or the swipe was mostly horizontal indicated by the X velocity
                            // being greater than the Y velocity, settle to the nearest correct height.
                            int currentTop = releasedChild.getTop();
                            if (fitToContents) {
                                if (Math.abs(currentTop - fitToContentsOffset)
                                        < Math.abs(currentTop - collapsedOffset)) {
                                    top = fitToContentsOffset;
                                    targetState = STATE_EXPANDED;
                                } else {
                                    top = collapsedOffset;
                                    targetState = STATE_COLLAPSED;
                                }
                            } else {
                                if (currentTop < halfExpandedOffset) {
                                    if (currentTop < Math.abs(currentTop - collapsedOffset)) {
                                        top = expandedOffset;
                                        targetState = STATE_EXPANDED;
                                    } else {
                                        top = halfExpandedOffset;
                                        targetState = STATE_HALF_EXPANDED;
                                    }
                                } else {
                                    if (Math.abs(currentTop - halfExpandedOffset)
                                            < Math.abs(currentTop - collapsedOffset)) {
                                        top = halfExpandedOffset;
                                        targetState = STATE_HALF_EXPANDED;
                                    } else {
                                        top = collapsedOffset;
                                        targetState = STATE_COLLAPSED;
                                    }
                                }
                            }
                        } else { // Moving Down
                            if (fitToContents) {
                                top = collapsedOffset;
                                targetState = STATE_COLLAPSED;
                            } else {
                                // Settle to the nearest correct height.
                                int currentTop = releasedChild.getTop();
                                if (Math.abs(currentTop - halfExpandedOffset)
                                        < Math.abs(currentTop - collapsedOffset)) {
                                    top = halfExpandedOffset;
                                    targetState = STATE_HALF_EXPANDED;
                                } else {
                                    top = collapsedOffset;
                                    targetState = STATE_COLLAPSED;
                                }
                            }
                        }
                        startSettlingAnimation(releasedChild, targetState, top, true);
                    }
    
                    @Override
                    public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                        return MathUtils.clamp(
                                top, getExpandedOffset(), hideable ? parentHeight : collapsedOffset);
                    }
    
                    @Override
                    public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                        return child.getLeft();
                    }
    
                    @Override
                    public int getViewVerticalDragRange(@NonNull View child) {
                        if (hideable) {
                            return parentHeight;
                        } else {
                            return collapsedOffset;
                        }
                    }
                };
    
        void dispatchOnSlide(int top) {
            View bottomSheet = viewRef.get();
            if (bottomSheet != null && !callbacks.isEmpty()) {
                float slideOffset =
                        (top > collapsedOffset || collapsedOffset == getExpandedOffset())
                                ? (float) (collapsedOffset - top) / (parentHeight - collapsedOffset)
                                : (float) (collapsedOffset - top) / (collapsedOffset - getExpandedOffset());
                for (int i = 0; i < callbacks.size(); i++) {
                    callbacks.get(i).onSlide(bottomSheet, slideOffset);
                }
            }
        }
    
        @VisibleForTesting
        int getPeekHeightMin() {
            return peekHeightMin;
        }
    
        /**
         * Disables the shaped corner {@link ShapeAppearanceModel} interpolation transition animations.
         * Will have no effect unless the sheet utilizes a {@link MaterialShapeDrawable} with set shape
         * theming properties. Only For use in UI testing.
         *
         * @hide
         */
        @RestrictTo(LIBRARY_GROUP)
        @VisibleForTesting
        public void disableShapeAnimations() {
            // Sets the shape value animator to null, prevents animations from occuring during testing.
            interpolatorAnimator = null;
        }
    
        private class SettleRunnable implements Runnable {
    
            private final View view;
    
            private boolean isPosted;
    
            @State
            int targetState;
    
            SettleRunnable(View view, @State int targetState) {
                this.view = view;
                this.targetState = targetState;
            }
    
            @Override
            public void run() {
                if (viewDragHelper != null && viewDragHelper.continueSettling(true)) {
                    ViewCompat.postOnAnimation(view, this);
                } else {
                    setStateInternal(targetState);
                }
                this.isPosted = false;
            }
        }
    
        /**
         * State persisted across instances
         */
        protected static class SavedState extends AbsSavedState {
            @State
            final int state;
            int peekHeight;
            boolean fitToContents;
            boolean hideable;
            boolean skipCollapsed;
    
            public SavedState(@NonNull Parcel source) {
                this(source, null);
            }
    
            public SavedState(@NonNull Parcel source, ClassLoader loader) {
                super(source, loader);
                //noinspection ResourceType
                state = source.readInt();
                peekHeight = source.readInt();
                fitToContents = source.readInt() == 1;
                hideable = source.readInt() == 1;
                skipCollapsed = source.readInt() == 1;
            }
    
            public SavedState(Parcelable superState, @NonNull MyViewPagerBottomSheetBehavior<?> behavior) {
                super(superState);
                this.state = behavior.getState();
                this.peekHeight = behavior.getPeekHeight();
                this.fitToContents = behavior.isFitToContents();
                this.hideable = behavior.isHideable();
                this.skipCollapsed = behavior.getSkipCollapsed();
            }
    
    
            @Deprecated
            public SavedState(Parcelable superstate, int state) {
                super(superstate);
                this.state = state;
            }
    
    
            @Override
            public void writeToParcel(@NonNull Parcel out, int flags) {
                super.writeToParcel(out, flags);
                out.writeInt(state);
                out.writeInt(peekHeight);
                out.writeInt(fitToContents ? 1 : 0);
                out.writeInt(hideable ? 1 : 0);
                out.writeInt(skipCollapsed ? 1 : 0);
            }
    
            public static final Creator<SavedState> CREATOR =
                    new ClassLoaderCreator<SavedState>() {
                        @NonNull
                        @Override
                        public SavedState createFromParcel(@NonNull Parcel in, ClassLoader loader) {
                            return new SavedState(in, loader);
                        }
    
                        @Nullable
                        @Override
                        public SavedState createFromParcel(@NonNull Parcel in) {
                            return new SavedState(in, null);
                        }
    
                        @NonNull
                        @Override
                        public SavedState[] newArray(int size) {
                            return new SavedState[size];
                        }
                    };
        }
    
        /**
         * A utility function to get the {@link BottomSheetBehavior} associated with the {@code view}.
         *
         * @param view The {@link View} with {@link BottomSheetBehavior}.
         * @return The {@link BottomSheetBehavior} associated with the {@code view}.
         */
        @NonNull
        @SuppressWarnings("unchecked")
        public static <V extends View> MyViewPagerBottomSheetBehavior<V> from(@NonNull V view) {
            ViewGroup.LayoutParams params = view.getLayoutParams();
            if (!(params instanceof CoordinatorLayout.LayoutParams)) {
                throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");
            }
            CoordinatorLayout.Behavior<?> behavior =
                    ((CoordinatorLayout.LayoutParams) params).getBehavior();
            if (!(behavior instanceof MyViewPagerBottomSheetBehavior)) {
                throw new IllegalArgumentException("The view is not associated with BottomSheetBehavior");
            }
            return (MyViewPagerBottomSheetBehavior<V>) behavior;
        }
    
        /**
         * Sets whether the BottomSheet should update the accessibility status of its {@link
         * CoordinatorLayout} siblings when expanded.
         *
         * <p>Set this to true if the expanded state of the sheet blocks access to siblings (e.g., when
         * the sheet expands over the full screen).
         */
        public void setUpdateImportantForAccessibilityOnSiblings(
                boolean updateImportantForAccessibilityOnSiblings) {
            this.updateImportantForAccessibilityOnSiblings = updateImportantForAccessibilityOnSiblings;
        }
    
        private void updateImportantForAccessibility(boolean expanded) {
            if (viewRef == null) {
                return;
            }
    
            ViewParent viewParent = viewRef.get().getParent();
            if (!(viewParent instanceof CoordinatorLayout)) {
                return;
            }
    
            CoordinatorLayout parent = (CoordinatorLayout) viewParent;
            final int childCount = parent.getChildCount();
            if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) && expanded) {
                if (importantForAccessibilityMap == null) {
                    importantForAccessibilityMap = new HashMap<>(childCount);
                } else {
                    // The important for accessibility values of the child views have been saved already.
                    return;
                }
            }
    
            for (int i = 0; i < childCount; i++) {
                final View child = parent.getChildAt(i);
                if (child == viewRef.get()) {
                    continue;
                }
    
                if (expanded) {
                    // Saves the important for accessibility value of the child view.
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        importantForAccessibilityMap.put(child, child.getImportantForAccessibility());
                    }
                    if (updateImportantForAccessibilityOnSiblings) {
                        ViewCompat.setImportantForAccessibility(
                                child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
                    }
                } else {
                    if (updateImportantForAccessibilityOnSiblings
                            && importantForAccessibilityMap != null
                            && importantForAccessibilityMap.containsKey(child)) {
                        // Restores the original important for accessibility value of the child view.
                        ViewCompat.setImportantForAccessibility(child, importantForAccessibilityMap.get(child));
                    }
                }
            }
    
            if (!expanded) {
                importantForAccessibilityMap = null;
            }
        }
    
        private void updateAccessibilityActions() {
            if (viewRef == null) {
                return;
            }
            V child = viewRef.get();
            if (child == null) {
                return;
            }
            ViewCompat.removeAccessibilityAction(child, AccessibilityNodeInfoCompat.ACTION_COLLAPSE);
            ViewCompat.removeAccessibilityAction(child, AccessibilityNodeInfoCompat.ACTION_EXPAND);
            ViewCompat.removeAccessibilityAction(child, AccessibilityNodeInfoCompat.ACTION_DISMISS);
    
            if (hideable && state != STATE_HIDDEN) {
                addAccessibilityActionForState(child, AccessibilityActionCompat.ACTION_DISMISS, STATE_HIDDEN);
            }
    
            switch (state) {
                case STATE_EXPANDED: {
                    int nextState = fitToContents ? STATE_COLLAPSED : STATE_HALF_EXPANDED;
                    addAccessibilityActionForState(
                            child, AccessibilityActionCompat.ACTION_COLLAPSE, nextState);
                    break;
                }
                case STATE_HALF_EXPANDED: {
                    addAccessibilityActionForState(
                            child, AccessibilityActionCompat.ACTION_COLLAPSE, STATE_COLLAPSED);
                    addAccessibilityActionForState(
                            child, AccessibilityActionCompat.ACTION_EXPAND, STATE_EXPANDED);
                    break;
                }
                case STATE_COLLAPSED: {
                    int nextState = fitToContents ? STATE_EXPANDED : STATE_HALF_EXPANDED;
                    addAccessibilityActionForState(child, AccessibilityActionCompat.ACTION_EXPAND, nextState);
                    break;
                }
                default: // fall out
            }
        }
    
        private void addAccessibilityActionForState(
                V child, AccessibilityActionCompat action, final int state) {
            ViewCompat.replaceAccessibilityAction(
                    child,
                    action,
                    null,
                    new AccessibilityViewCommand() {
                        @Override
                        public boolean perform(@NonNull View view, @Nullable CommandArguments arguments) {
                            setState(state);
                            return true;
                        }
                    });
        }
    }
    
    

    相关文章

      网友评论

        本文标题:BottomSheetBehavior+ViewPager+多R

        本文链接:https://www.haomeiwen.com/subject/sclujktx.html