美文网首页
Android-自定义侧滑菜单

Android-自定义侧滑菜单

作者: h2coder | 来源:发表于2022-07-28 11:23 被阅读0次

    效果

    SwipeMenuLayout.gif

    项目地址

    SwipeMenuLayout

    自定义属性

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <declare-styleable name="SwipeMenuLayout">
            <!-- 是否左滑 -->
            <attr name="sml_left_swipe" format="boolean" />
            <!-- 侧滑是否可用 -->
            <attr name="sml_swipe_enable" format="boolean" />
        </declare-styleable>
    </resources>
    

    代码实现

    public class SwipeMenuLayout extends FrameLayout {
        /**
         * 内容区域View
         */
        private View vContentView;
        /**
         * 菜单区域View
         */
        private View vMenuView;
        /**
         * 拽托帮助类
         */
        private ViewDragHelper mViewDragHelper;
        /**
         * 菜单状态改变监听
         */
        private OnMenuStateChangeListener mMenuStateChangeListener;
        /**
         * 触摸按下时的X坐标
         */
        private float mDownX;
        /**
         * 触摸按下时的Y坐标
         */
        private float mDownY;
        /**
         * 是否左滑打开菜单
         */
        private boolean isLeftSwipe;
        /**
         * 侧滑功能是否可用,默认开
         */
        private boolean isSwipeEnable = true;
    
        public SwipeMenuLayout(Context context) {
            this(context, null);
        }
    
        public SwipeMenuLayout(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public SwipeMenuLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(context, attrs, defStyleAttr);
        }
    
        private void init(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            mViewDragHelper = ViewDragHelper.create(this, 0.5f, new ViewDragHelperCallBack());
            initAttr(context, attrs, defStyleAttr);
        }
    
        private void initAttr(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SwipeMenuLayout, defStyleAttr, 0);
            //是否左滑打开菜单
            isLeftSwipe = typedArray.getBoolean(R.styleable.SwipeMenuLayout_sml_left_swipe, true);
            //侧滑是否可用
            isSwipeEnable = typedArray.getBoolean(R.styleable.SwipeMenuLayout_sml_swipe_enable, true);
            typedArray.recycle();
        }
    
        private class ViewDragHelperCallBack extends ViewDragHelper.Callback {
            /**
             * 开始拽托时的X坐标
             */
            private int mDownX;
            /**
             * 开始拽托时的Y坐标
             */
            private int mDownY;
    
            @Override
            public boolean tryCaptureView(@NonNull View child, int pointerId) {
                //内容区域可以拽托
                return child == vContentView;
            }
    
            @Override
            public void onViewCaptured(@NonNull View capturedChild, int activePointerId) {
                super.onViewCaptured(capturedChild, activePointerId);
                mDownX = capturedChild.getLeft();
                mDownY = capturedChild.getTop();
            }
    
            @Override
            public int getViewHorizontalDragRange(@NonNull View child) {
                //水平方向的拖拽范围
                return vMenuView.getWidth();
            }
    
            @Override
            public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) {
                //菜单区域的宽度
                int menuViewWidth = vMenuView.getWidth();
                if (isLeftSwipe) {
                    //不能向左滑动出界限
                    if (left > 0) {
                        return 0;
                    } else if (left < -menuViewWidth) {
                        //向右滑动,不能超过可滑动的距离
                        return -menuViewWidth;
                    } else {
                        //在上面指定范围内,可滑动
                        return left;
                    }
                } else {
                    if (left < 0) {
                        //向左滑动,不能滑动出左侧屏幕
                        return 0;
                    } else if (left > menuViewWidth) {
                        //向右滑动,不能超过菜单宽度
                        return menuViewWidth;
                    } else {
                        //其他范围可滑动
                        return left;
                    }
                }
            }
    
            @Override
            public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) {
                super.onViewPositionChanged(changedView, left, top, dx, dy);
                //拽托内容布局,让菜单布局跟着移动
                int newLeft = vMenuView.getLeft() + dx;
                int right = newLeft + vMenuView.getWidth();
                vMenuView.layout(newLeft, top, right, getBottom());
            }
    
            @Override
            public void onViewDragStateChanged(int state) {
                super.onViewDragStateChanged(state);
                boolean isMenuOpen;
                boolean isMenuClose;
                int menuViewLeft = vMenuView.getLeft();
                int menuViewWidth = vMenuView.getWidth();
                int openMenuLeft;
                int closeMenuLeft;
                if (isLeftSwipe) {
                    //打开时,菜单的左边位置
                    openMenuLeft = getRight() - menuViewWidth;
                    //关闭时,菜单的左边位置
                    closeMenuLeft = getRight();
                } else {
                    openMenuLeft = 0;
                    closeMenuLeft = -menuViewWidth;
                }
                isMenuOpen = menuViewLeft == openMenuLeft;
                isMenuClose = menuViewLeft == closeMenuLeft;
                //处理开、关菜单回调
                if (state == ViewDragHelper.STATE_IDLE) {
                    //菜单开
                    if (isMenuOpen) {
                        onMenuOpenFinish();
                    } else if (isMenuClose) {
                        //菜单关
                        onMenuCloseFinish();
                    }
                }
            }
    
            @Override
            public int clampViewPositionVertical(@NonNull View child, int top, int dy) {
                //拽托子View纵向滑动时回调,锁定顶部padding距离即可,不能不复写,否则少了顶部的padding,位置就偏去上面了
                return 0;
            }
    
            @Override
            public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                int contentViewWidth = vContentView.getWidth();
                //手指释放时回调
                final int currentLeft = releasedChild.getLeft();
                final int currentTop = releasedChild.getTop();
                //计算移动距离
                int distanceX = currentLeft - mDownX;
                int distanceY = currentTop - mDownY;
                //松手回弹,如果右边剩余的距离大于Menu的一半,则滚动到最后边,否则滚动回最左边
                float halfMenuWidth = vMenuView.getWidth() / 2f;
                //判断是否是左右滑,上下滑不需要动
                if (Math.abs(distanceX) >= Math.abs(distanceY)) {
                    if (isLeftSwipe) {
                        //打开时的宽度,内容区域宽度减去一个菜单区域的宽度
                        float fullOpenWidth = contentViewWidth - halfMenuWidth;
                        //判断方向,向左滑动到打开,打开菜单
                        if (releasedChild.getRight() < fullOpenWidth) {
                            smoothOpenMenu();
                        } else {
                            //其他情况,关闭菜单
                            smoothClose();
                        }
                    } else {
                        //左滑,菜单露出超过一半,那么打开
                        if (vMenuView.getRight() > halfMenuWidth) {
                            smoothOpenMenu();
                        } else {
                            //没有露出一半,那么关闭
                            smoothClose();
                        }
                    }
                }
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            super.onLayout(changed, left, top, right, bottom);
            //布局内容区域,和父View一样大小
            vContentView.layout(left, top, right, bottom);
            //布局菜单区域,内容在左,菜单在内容区域的右边
            if (isLeftSwipe) {
                vMenuView.layout(right, top, right + vMenuView.getMeasuredWidth(), bottom);
            } else {
                //内容在右,菜单在内容区域的左边
                vMenuView.layout(left - vMenuView.getMeasuredWidth(), top, left, bottom);
            }
        }
    
        @Override
        protected void onFinishInflate() {
            super.onFinishInflate();
            int childCount = getChildCount();
            if (childCount > 2 || childCount <= 0) {
                throw new RuntimeException("子View必须只有2个,内容布局和菜单布局");
            }
            vContentView = getChildAt(0);
            vMenuView = getChildAt(1);
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            if (!isSwipeEnable) {
                return super.onInterceptTouchEvent(ev);
            }
            //将onInterceptTouchEvent委托给ViewDragHelper
            return mViewDragHelper.shouldInterceptTouchEvent(ev);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (!isSwipeEnable) {
                return super.onTouchEvent(event);
            }
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDownX = event.getX();
                    mDownY = event.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float moveX = event.getX();
                    float moveY = event.getY();
                    //横向滑动距离
                    float distanceX = Math.abs(moveX - mDownX);
                    //纵向滑动距离
                    float distanceY = Math.abs(moveY - mDownY);
                    //横向滑动得多,让外层不拦截事件
                    if (distanceX > distanceY) {
                        requestDisallowInterceptTouchEvent(true);
                    }
                    break;
                default:
                    break;
            }
            //将onTouchEvent委托给ViewDragHelper
            mViewDragHelper.processTouchEvent(event);
            return true;
        }
    
        @Override
        public void computeScroll() {
            super.computeScroll();
            //判断是否移动到头了,未到头则继续
            if (mViewDragHelper != null) {
                if (mViewDragHelper.continueSettling(true)) {
                    invalidate();
                }
            }
        }
    
        /**
         * 向左移动,打开菜单
         */
        public void smoothOpenMenu() {
            int finalLeft;
            //左滑,内容向左移动一个菜单布局的宽度
            if (isLeftSwipe) {
                finalLeft = -vMenuView.getWidth();
            } else {
                //右滑,内容向右移动一个菜单布局的宽度
                finalLeft = vMenuView.getWidth();
            }
            mViewDragHelper.smoothSlideViewTo(vContentView, finalLeft, vContentView.getTop());
            ViewCompat.postInvalidateOnAnimation(this);
        }
    
        /**
         * 向右移动,关闭菜单
         */
        public void smoothClose() {
            mViewDragHelper.smoothSlideViewTo(vContentView, 0, vContentView.getTop());
            ViewCompat.postInvalidateOnAnimation(this);
        }
    
        /**
         * 当菜单打开完成时调用
         */
        private void onMenuOpenFinish() {
            if (mMenuStateChangeListener != null) {
                mMenuStateChangeListener.onOpenMenu();
            }
        }
    
        /**
         * 当菜单关闭完成时调用
         */
        private void onMenuCloseFinish() {
            if (mMenuStateChangeListener != null) {
                mMenuStateChangeListener.onCloseMenu();
            }
        }
    
        public interface OnMenuStateChangeListener {
            /**
             * 当打开菜单时回调
             */
            void onOpenMenu();
    
            /**
             * 当关闭菜单时回调
             */
            void onCloseMenu();
        }
    
        /**
         * 添加菜单状态改变监听
         *
         * @param listener 监听器
         */
        public void addOnMenuStateChangeListener(OnMenuStateChangeListener listener) {
            this.mMenuStateChangeListener = listener;
        }
    
        /**
         * 设置侧滑功能是否可用
         */
        public void setSwipeEnable(boolean swipeEnable) {
            isSwipeEnable = swipeEnable;
        }
    
        /**
         * 侧滑功能是否可用
         */
        public boolean isSwipeEnable() {
            return isSwipeEnable;
        }
    }
    

    使用

    • 布局xml
    <?xml version="1.0" encoding="utf-8"?>
    <com.zh.android.swipemenulayoutsample.SwipeMenuLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/swipe_menu_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:sml_left_swipe="true"
        app:sml_swipe_enable="true">
    
        <!-- 内容View -->
        <TextView
            android:id="@+id/content_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="左滑动进入懒人模式"
            android:textColor="@android:color/black"
            android:textSize="18sp" />
    
        <!-- 菜单View -->
        <com.zh.android.swipemenulayoutsample.lazymode.LazyModeEntryView
            android:id="@+id/lazy_mode_entry_view"
            android:layout_width="wrap_content"
            android:layout_height="match_parent" />
    </com.zh.android.swipemenulayoutsample.SwipeMenuLayout>
    
    • Java代码
    final SwipeMenuLayout swipeMenuLayout = findViewById(R.id.swipe_menu_layout);
    //设置侧滑是否可用
    swipeMenuLayout.setSwipeEnable(true);
    //设置侧滑状态变化监听
    swipeMenuLayout.addOnMenuStateChangeListener(new SwipeMenuLayout.OnMenuStateChangeListener() {
        @Override
        public void onOpenMenu() {
            //侧滑打开
            swipeMenuLayout.smoothClose();
            startActivity(new Intent(MainActivity.this, LazyModeActivity.class));
        }
    
        @Override
        public void onCloseMenu() {
            //侧滑关闭
        }
    });
    

    相关文章

      网友评论

          本文标题:Android-自定义侧滑菜单

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