为之前的自定义View添加DrawerLayout(侧拉抽屉),为自定义View系列画上完美句号。(解决DrawerLayout与ListView滑动冲突)

版权声明:本文是博主原创文章,不得转载本文任何内容。原文地址: https://blog.csdn.net/smile_Running/article/details/81981965

                         【声明】:未经许可,禁止转载!

· 续篇

     继续上篇余下的内容:ListView添加下拉刷新、上拉加载,其实很简单

    当你阅读这篇文章时,你可能会一头雾水。原因呢,因为这是我的一个ListView系列的文章,从零到一,全部是自定义View实现的,当然你也许说为什么不用第三方库。这就是差距了,虽然我们可能做不到第三方库严谨的逻辑、漂亮的UI特效,但是这是我们实实在在自己写的。在我写完这几篇文章之后,我发现我的自定义View的水平不断上升,从之前对它的惧怕、到懵懂、到简单的UI、再到现。可谓是一路的成长,在逻辑上严谨了许多。

    今天,我们来为它实现一个侧滑抽屉的效果,比如我们最熟悉的QQ侧滑抽屉。当然,我的这一系列文章的UI其实也是随着QQ、以及大部分APP的潮流。那么看一下我们自定义侧滑菜单的效果图:

可能,我接连下来的几篇会越说越范,没有刚开始那么细。因为,你一旦理解了,自然就能明白我的表达和思路。不理解的话,哈哈,继续看我之前写的比较详细的吧。

首先,我们说一下这个抽屉的思路:

1、抽屉位置,肯定是左侧、或右侧的屏幕外的区域。那就得将它布局到屏幕外,算准它的坐标,然后通过手指滑动显示出来。

2、解决冲突问题,这里规定的是我的抽屉在左侧。

抽屉的状态有关、开

      一、前提是在抽屉并没打开,判断手指确实是往右移动的动作,要打开抽屉的行为,这时候才可以移动抽屉。那么在这个时候,拦截touch事件,将抽屉移动显示出来。

      二、抽屉打开了,这个时候要去判断抽屉内是否有滚动视图、和点击事件的传递行为。如果有,要分条件拦截;如果没有,全部拦截。

      三、与下拉刷新的冲突,因为处在下拉刷新的行为,这时候我们不能够拉出抽屉,所以要剥夺父视图的touch事件。

首先来看看布局和布局的处理代码:

<listview.example.x.slidelistview.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="listview.example.x.slidelistview.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginRight="72dp"
        android:orientation="vertical">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="180dp">

            <de.hdodenhof.circleimageview.CircleImageView
                android:id="@+id/img"
                android:layout_width="120dp"
                android:layout_height="120dp"
                android:layout_gravity="center"
                android:src="@drawable/img_7" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|center_horizontal"
                android:text="_Xu2WeI"
                android:textSize="20sp" />
        </FrameLayout>

        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

    <listview.example.x.slidelistview.RefreshLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="180dp"
            android:background="@android:color/holo_red_dark"
            android:gravity="center_horizontal">

            <ProgressBar
                android:id="@+id/refresh_progress"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:layout_gravity="bottom|right"
                tools:ignore="RtlHardcoded" />

            <TextView
                android:id="@+id/tv_refresh_state"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|center_horizontal"
                android:layout_marginBottom="32dp"
                android:layout_marginTop="8dp"
                android:textColor="@android:color/white" />

            <ImageView
                android:id="@+id/iv_refreshing"
                android:layout_width="32dp"
                android:layout_height="32dp"
                android:layout_gravity="center_horizontal|bottom"
                android:src="@drawable/ic_flight_black_24dp" />
        </FrameLayout>

        <ListView
            android:id="@+id/lv_contact"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="80dp"
            android:background="@android:color/holo_red_dark"
            android:gravity="center">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center_horizontal"
                android:orientation="vertical">

                <FrameLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content">

                    <ImageView
                        android:id="@+id/ivLoadingIcon"
                        android:layout_width="32dp"
                        android:layout_height="32dp"
                        android:layout_gravity="center_horizontal"
                        android:src="@drawable/ic_flight_black_24dp" />

                    <ProgressBar
                        android:id="@+id/load_progress"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </FrameLayout>

                <TextView
                    android:id="@+id/tv_load_state"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="正在加载"
                    android:textColor="@android:color/white" />
            </LinearLayout>

        </RelativeLayout>
    </listview.example.x.slidelistview.RefreshLayout>

</listview.example.x.slidelistview.DrawerLayout>

首先是拦截事件的处理代码

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        final float x = event.getX();
        final float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                final float dx = x - startX;
                final float dy = y - startX;
                /**
                 * 判断抽屉未打开时
                 */
                if (!isDrawerOpen) {
                    /**
                     * 抽屉右滑
                     */
                    if (x - startX > 0 && dx > 0) { // 右滑
                        intercept = true;
                    }
                } else {
                    /**
                     * 抽屉打开,并非拦截所有事件,也许抽屉里还有滚动,这里要做出判断。
                     * 全拦截,则抽屉子菜单选项滚动不了
                     */
                    final int disX = (int) Math.abs(x - startX);
                    final int disY = (int) Math.abs(y - startY);
                    if (disX > disY && disX > 15) {
                        intercept = true;
                    }
                    //如果抽屉里有滚动列表,则不拦截它
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return intercept;
    }

接着是抽屉移动事件:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                moveX = startX = x;
                moveY = startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                final float dx = x - startX;
                final float dy = y - startY;

                int disX = (int) (getScrollX() - dx);
                scrollTo(disX, getScrollY());

                if (x - startX < 0) {
                    isMoveLeft = true;
                } else if (x - startX > 0) {
                    isMoveLeft = false;
                }

                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_UP:
                /**
                 * 抽屉未打开
                 */
                if (!isDrawerOpen) {
                    if (-getScrollX() > mDrawerWidth / 3) {
                        openDrawer();
                    } else {
                        closeDrawer();
                    }
                } else {
                    /**
                     * 抽屉是开着
                     */
                    if (isMoveLeft) { /** 产生左移行为,关闭抽屉 **/
                        if (-getScrollX() > 15) { //至少滑动一点距离
                            closeDrawer();
                        }
                    } else {
                        /** 产生右移行为,恢复打开时状态 **/
                        openDrawer();
                    }
                }
                break;
        }
        return true;
    }

还有就是在下拉刷新或者是上拉加载的时候,剥夺DrawerLayout的touch事件处理权。限制在下拉刷新或者是上拉加载的时候打开抽屉。是在之前的下拉刷新或者是上拉加载添加剥夺事件的代码(注意:此方法是RefreshLayout类中的中断事件代码,并非本类。):

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final float x = ev.getX();
        final float y = ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isIntercept = false;
                upX = downX = x;
                upY = downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isTop) {
                    /** 下拉刷新拦截 **/
                    if (upY - y < 0) {
                        /**
                         * 在下拉刷新行为时,拦截Listview的滚动事件
                         */
                        isIntercept = true;
                        /**
                         * 剥夺DrawerLayout的touch事件
                         */
                        getParent().requestDisallowInterceptTouchEvent(true);
                    } else if (y - upY < 0) {
                        isIntercept = false;
                    }
                } else if (isBottom) {
                    /** 上拉加载拦截 **/
                    if (y - downY < 0) {
                        /**
                         * 上拉加载时,拦截Listview的滚动事件
                         */
                        isIntercept = true;
                        getParent().requestDisallowInterceptTouchEvent(true);
                    } else if (y - downY > 0) {
                        isIntercept = false;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                downX = upY = 0;
                downX = upX = 0;
                break;
        }
        return isIntercept;
    }

自定义DrawerLayout类的完整代码:

/**
 * @Created by xww.
 * @Creation time 2018/8/23.
 */

public class DrawerLayout extends FrameLayout {

    private View mDrawerView;
    private View mContentView;

    private int mDrawerWidth;
    private int mDrawerHeight;
    private int mContentWidth;
    private int mContentHeight;

    private Scroller mScroller;

    private float startX;
    private float startY;
    private float moveX;
    private float moveY;

    private boolean isDrawerOpen;
    private boolean isMoveLeft;
    private boolean isVerticalScroll;

    public DrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mDrawerView = getChildAt(0);
        mContentView = getChildAt(1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        mDrawerWidth = mDrawerView.getMeasuredWidth();
        mDrawerHeight = mDrawerView.getMeasuredHeight();
        mContentWidth = mContentView.getMeasuredWidth();
        mContentHeight = mContentView.getMeasuredHeight();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mDrawerView.layout(-mDrawerWidth, 0, 0, mDrawerHeight);
        mContentView.layout(0, 0, mContentWidth, mContentHeight);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                moveX = startX = x;
                moveY = startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                final float dx = x - startX;
                final float dy = y - startY;

                int disX = (int) (getScrollX() - dx);
                if (-disX >= mDrawerWidth) {
                    disX = -mDrawerWidth;
                }
                scrollTo(disX, getScrollY());

                if (x - startX < 0) {
                    isMoveLeft = true;
                } else if (x - startX > 0) {
                    isMoveLeft = false;
                }

                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_UP:
                /**
                 * 抽屉未打开
                 */
                if (!isDrawerOpen) {
                    if (-getScrollX() > mDrawerWidth / 3) {
                        openDrawer();
                    } else {
                        closeDrawer();
                    }
                } else {
                    /**
                     * 抽屉是开着
                     */
                    if (isMoveLeft) { /** 产生左移行为,关闭抽屉 **/
                        if (-getScrollX() > 15) { //至少滑动一点距离
                            closeDrawer();
                        }
                    } else {
                        /** 产生右移行为,恢复打开时状态 **/
                        openDrawer();
                    }
                }
                break;
        }
        return true;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        final float x = event.getX();
        final float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = x;
                startY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                final float dx = x - startX;
                final float dy = y - startX;
                /**
                 * 判断抽屉未打开时
                 */
                if (!isDrawerOpen) {
                    /**
                     * 抽屉右滑
                     */
                    if (x - startX > 0 && dx > 0) { // 右滑
                        intercept = true;
                    }
                } else {
                    /**
                     * 抽屉打开,并非拦截所有事件,也许抽屉里还有滚动,这里要做出判断。
                     * 全拦截,则抽屉子菜单选项滚动不了
                     */
                    final int disX = (int) Math.abs(x - startX);
                    final int disY = (int) Math.abs(y - startY);
                    if (disX > disY && disX > 15) {
                        intercept = true;
                    }
                    //如果抽屉里有滚动列表,则不拦截它
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return intercept;
    }

    private void openDrawer() {
        isDrawerOpen = true;
        mScroller.startScroll(getScrollX(), getScrollY(), -getScrollX() - mDrawerWidth, 0);
        invalidate();
    }

    private void closeDrawer() {
        isDrawerOpen = false;
        mScroller.startScroll(getScrollX(), getScrollY(), -getScrollX(), 0);
        invalidate();
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }
}

源码下载:

CSDN资源链接:自定义ViewGroup仿QQ侧拉删除、侧拉抽屉;下拉刷新、上拉加载动画

©版权所有:https://blog.csdn.net/smile_Running/article/details/81981965

猜你喜欢

转载自blog.csdn.net/smile_Running/article/details/81981965