Android-自定义侧滑菜单

2022-07-28  本文已影响0人  h2coder

效果

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 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>
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() {
        //侧滑关闭
    }
});
上一篇下一篇

猜你喜欢

热点阅读