(转)ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突

最近在做项目,涉及到类似QQ的页面的滑动。但是却遇到了侧滑和ViewPager冲突的问题,头疼了很长时间,最后在网上发现了这篇博客,转载过来供自己学习参考(写这篇博客的原创作者,因为我发现这篇博客的地方也是别人转载的,所以具体是有哪位大牛完成的我也不得而知。转载文章不能标出原创作者和出处,请见谅)

QQ5.0的侧滑效果有多种实现方式,

如http://blog.csdn.net/lmj623565791/article/details/39257409 就是利用HorizontalScrollView实现的,简单实用;

如http://blog.csdn.net/manoel/article/details/39013095/ 通过改造SlidingMenu实现,没有改变原有SlidingMenu功能,屏幕边缘侧滑也可以....

相对来说ViewDragHelper实现方式最为复杂,但灵活性也更高,可以应对各种需求,毕竟google的DrawerLayout也是用ViewDragHelper实现的。

先看下效果:

分析:主面板有个ViewPager,需要注意的是viewpager的滑动肯定与侧滑相互冲突,一般我们让viewpager的第一页是可以向右拖出侧滑菜单,其他页则响应viewpager的滑动。

代码:

自定义侧滑控件DragLayout ,代码量不少,不过关键地方都加了注释。

package com.liujing.draglayoutdemo;/**
 * 通过ViewDragHelper实现的侧滑控件
 * @author liujing
 *
 */
public class DragLayout extends FrameLayout {

    private View mLeftContent;
    private View mMainContent;
    private int mWidth;
    private int mDragRange;
    private ViewDragHelper mDragHelper;
    private int mMainLeft;
    private int mHeight;

    private Status mStatus = Status.Close;
    private GestureDetectorCompat mDetectorCompat;

    public DragLayout(Context context) {
        this(context, null);
    }

    public DragLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DragLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        //ViewDragHelper.create(forParent, sensitivity, cb);
        //对应参数:父布局、敏感度、回调
        mDragHelper = ViewDragHelper.create(this, mCallBack);
        mDetectorCompat = new GestureDetectorCompat(getContext(),
                mGestureListener);

    }

    private boolean isDrag = true;

    public void setDrag(boolean isDrag) {
        this.isDrag = isDrag;
        if(isDrag){
            //这里有个Bug,当isDrag从false变为true是,mDragHelper的mCallBack在
            //首次滑动时不响应,再次滑动才响应,只好在此调用下,让mDragHelper恢复下状态
            mDragHelper.abort();
        }
    }

    SimpleOnGestureListener mGestureListener = new SimpleOnGestureListener() {
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY) {
        
            if((Math.abs(distanceX) > Math.abs(distanceY))&&distanceX<0&&isDrag!=false&&mStatus==Status.Close){
                return true;
            }else if((Math.abs(distanceX) > Math.abs(distanceY))&&distanceX>0&&isDrag!=false&&mStatus==Status.Open){
                return true;
            }else {
                return false;
            }
        };
    };

    ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() {
        public void onEdgeTouched(int edgeFlags, int pointerId) {

        };

        public void onEdgeDragStarted(int edgeFlags, int pointerId) {
             mDragHelper.captureChildView(mMainContent, pointerId);
        };
        // 决定child是否可被拖拽。返回true则进行拖拽。
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child == mMainContent || child == mLeftContent;
        }

        // 当capturedChild被拖拽时
        @Override
        public void onViewCaptured(View capturedChild, int activePointerId) {
            super.onViewCaptured(capturedChild, activePointerId);
        }

        // 横向拖拽的范围,大于0时可拖拽,等于0无法拖拽
        // 此方法只用于计算如view释放速度,敏感度等
        // 实际拖拽范围由clampViewPositionHorizontal方法设置
        @Override
        public int getViewHorizontalDragRange(View child) {
            return mDragRange;
        }

        // 此处设置view的拖拽范围。(实际移动还未发生)
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            // 拖动前oldLeft + 变化量dx == left
            if (mMainLeft + dx < 0) {
                return 0;
            } else if (mMainLeft + dx > mDragRange) {
                return mDragRange;
            }
            return left;
        }

        // 决定了当View位置改变时,希望发生的其他事情。(此时移动已经发生)
        // 高频实时的调用,在这里设置左右面板的联动
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            //如果拖动的是主面板
            if (changedView == mMainContent) {
                mMainLeft = left;
            } else {
                mMainLeft += dx;
            }

            // 进行值的修正
            if (mMainLeft < 0) {
                mMainLeft = 0;
            } else if (mMainLeft > mDragRange) {
                mMainLeft = mDragRange;
            }
            // 如果拖拽的是左面板,强制在指定位置绘制Content
            if (changedView == mLeftContent) {
                layoutContent();
            }

            dispatchDragEvent(mMainLeft);

        }

        // View被释放时,侧滑打开或恢复
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (xvel > 0) {
                open();
            } else if (xvel == 0 && mMainLeft > mDragRange * 0.5f) {
                open();
            } else {
                close();
            }

        }

        //当拖拽状态改变的时,IDLE/DRAGGING/SETTLING
        @Override
        public void onViewDragStateChanged(int state) {
            super.onViewDragStateChanged(state);
        }

    };

    private void layoutContent() {
        mMainContent.layout(mMainLeft, 0, mMainLeft + mWidth, mHeight);
        mLeftContent.layout(0, 0, mWidth, mHeight);
    }

    /**
     * 每次更新都会调用 根据当前执行的位置计算百分比percent
     */
    protected void dispatchDragEvent(int mainLeft) {
        float percent = mainLeft / (float) mDragRange;
        animViews(percent);

        if (mListener != null) {
            mListener.onDraging(percent);
        }

        Status lastStatus = mStatus;
        if (updateStatus(mainLeft) != lastStatus) {
            if (mListener == null) {
                return;
            }
            if (lastStatus == Status.Draging) {
                if (mStatus == Status.Close) {
                    mListener.onClose();
                } else if (mStatus == Status.Open) {
                    mListener.onOpen();
                }

            }
        }
    }

    public static interface OnLayoutDragingListener {
        void onOpen();

        void onClose();

        void onDraging(float percent);
    }

    private OnLayoutDragingListener mListener;

    public void setOnLayoutDragingListener(OnLayoutDragingListener l) {
        mListener = l;
    }

    private Status updateStatus(int mainLeft) {
        if (mainLeft == 0) {
            mStatus = Status.Close;
        } else if (mainLeft == mDragRange) {
            mStatus = Status.Open;
        } else {
            mStatus = Status.Draging;
        }
        return mStatus;
    }

    public static enum Status {
        Open, Close, Draging
    }

    public Status getStatus() {
        return mStatus;
    }

    public void setStatus(Status mStatus) {
        this.mStatus = mStatus;
    }

    /**
     * 伴随动画:
     * @param percent
     */
    private void animViews(float percent) {
        // 主面板:缩放
        float inverse = 1 - percent * 0.2f;
        ViewHelper.setScaleX(mMainContent, inverse);
        ViewHelper.setScaleY(mMainContent, inverse);

        // 左面板:缩放、平移、透明度
        ViewHelper.setScaleX(mLeftContent, 0.5f + 0.5f * percent);
        ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);

        ViewHelper.setTranslationX(mLeftContent, -mWidth / 2.0f + mWidth / 2.0f
                * percent);
        ViewHelper.setAlpha(mLeftContent, percent);
        // 背景:颜色渐变
        getBackground().setColorFilter(
                evaluate(percent, Color.BLACK, Color.TRANSPARENT),
                PorterDuff.Mode.SRC_OVER);
    }

    private int evaluate(float fraction, int startValue, int endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int) ((startA + (int) (fraction * (endA - startA))) << 24)
                | (int) ((startR + (int) (fraction * (endR - startR))) << 16)
                | (int) ((startG + (int) (fraction * (endG - startG))) << 8)
                | (int) ((startB + (int) (fraction * (endB - startB))));
    }

    @Override
    public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
        boolean onTouchEvent = mDetectorCompat.onTouchEvent(ev);
        //将Touch事件传递给ViewDragHelper
        return mDragHelper.shouldInterceptTouchEvent(ev) & onTouchEvent;
    };

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        try {
            //将Touch事件传递给ViewDragHelper
            mDragHelper.processTouchEvent(event);
        } catch (Exception e) {
        }
        return true;
    }

    public void close() {
        close(true);
    };

    public void open() {
        open(true);
    }

    public void close(boolean isSmooth) {
        mMainLeft = 0;
        if (isSmooth) {
            // 执行动画,返回true代表有未完成的动画, 需要继续执行
            if (mDragHelper.smoothSlideViewTo(mMainContent, mMainLeft, 0)) {
                // 注意:参数传递根ViewGroup
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            layoutContent();
        }
    }

    public void open(boolean isSmooth) {
        mMainLeft = mDragRange;
        if (isSmooth) {
            if (mDragHelper.smoothSlideViewTo(mMainContent, mMainLeft, 0)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            layoutContent();
        }
    }

    @Override
    public void computeScroll() {
        // 高频率调用,决定是否有下一个变动等待执行
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        mMainContent.layout(mMainLeft, 0, mMainLeft + mWidth, mHeight);
        mLeftContent.layout(0, 0, mWidth, mHeight);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //拿到宽高
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        //设置拖动范围
        mDragRange = (int) (mWidth * 0.6f);
    }

    /**
     * 填充结束时获得两个子布局的引用
     */
    @Override
    protected void onFinishInflate() {

        int childCount = getChildCount();
        // 必要的检验
        if (childCount < 2) {
            throw new IllegalStateException(
                    "You need two childrens in your content");
        }

        if (!(getChildAt(0) instanceof ViewGroup)
                || !(getChildAt(1) instanceof ViewGroup)) {
            throw new IllegalArgumentException(
                    "Your childrens must be an instance of ViewGroup");
        }

        mLeftContent = getChildAt(0);
        mMainContent = getChildAt(1);
    }

}
 

我设置了一个isDrag的标签来控制是否允许侧滑;

在SimpleOnGestureListener的onScroll方法中判断,如果是横向向右滑动,且侧滑是关闭状态,且isDrag的tag为true时,让ViewDragHelper响应对应滑动事件(滑出),

如果是横向向左滑动,且侧滑是开启状态,且isDrag的tag为true时,让ViewDragHelper响应对应滑动事件(滑入),

其余情况,都不处理;

在onViewPositionChanged里通过dispatchDragEvent方法,计算移动百分比,据此执行相应的伴随动画,同时也将该值通过回调传递到外面,执行动画用了nineoldandroids来兼容之前版本。

MainContentLayout是为了处理当侧滑菜单打开后,主面板便不再响应内部的Touch事件了。

package com.liujing.draglayoutdemo;public class MainContentLayout extends RelativeLayout {

    private DragLayout mDragLayout;

    public MainContentLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MainContentLayout(Context context) {
        super(context);
    }

    public void setDragLayout(DragLayout mDragLayout) {
        this.mDragLayout = mDragLayout;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(mDragLayout.getStatus() == Status.Close){
            return super.onInterceptTouchEvent(ev);
        }else {
            return true;
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(mDragLayout.getStatus() == Status.Close){
            return super.onTouchEvent(event);
        }else {
            if(MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP){
                mDragLayout.close();
            }
            return true;
        }

    }

}
DragLayout设置回调监听:
mDragLayout.setOnLayoutDragingListener(new OnLayoutDragingListener() {

            @Override
            public void onOpen() {
                //打开
            }
            @Override
            public void onDraging(float percent) {
                //滑动中
            }
            @Override
            public void onClose() {
                //关闭
            }
        });

当ViewPager切换时,只要给DragLayout设置是否允许侧滑即可

public void onPageSelected(int postion) {
            switch (postion) {
            case 0:
                mDragLayout.setDrag(true);
                break;
            case 1:
                mDragLayout.setDrag(false);
                break;
            case 2:
                mDragLayout.setDrag(false);
                break;
            }
        }

布局示例:

<com.liujing.draglayoutdemo.DragLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/dl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/img_frame_background" >

    <LinearLayout
        android:id="@+id/fl_menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="40dp"
        android:paddingLeft="10dp"
        android:paddingTop="50dp" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#ffffff"
            android:textSize="18sp"
            android:text="这是左面板" >
        </TextView>
    </LinearLayout>

    <com.liujing.draglayoutdemo.MainContentLayout
        android:id="@+id/mainContent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/tab_bg"
        android:orientation="vertical" >

        <android.support.v4.view.ViewPager
            android:id="@+id/pager_view"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_below="@id/topbar" />
    </com.liujing.draglayoutdemo.MainContentLayout>

</com.liujing.draglayoutdemo.DragLayout>
时间: 2024-10-13 15:11:56

(转)ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突的相关文章

ViewDragHelper实现QQ5.0侧滑并处理与ViewPager的滑动冲突

QQ5.0的侧滑效果有多种实现方式, 如http://blog.csdn.net/lmj623565791/article/details/39257409   就是利用HorizontalScrollView实现的,简单实用; 如http://blog.csdn.net/manoel/article/details/39013095/   通过改造SlidingMenu实现,没有改变原有SlidingMenu功能,屏幕边缘侧滑也可以.... 相对来说ViewDragHelper实现方式最为复杂

Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭【学习鸿洋_视频博客笔记总结】

学习鸿洋博客:http://blog.csdn.net/lmj623565791/article/details/39257409 学习鸿洋视频:慕课网视频 看看Android 高仿 QQ5.0 侧滑菜单效果 自定义控件实现效果: 技术上,继承HorizontalScrollView 加上自定义ViewGroup来实现: 1.onMeasure:决定内部View(子View)的宽和高,以及自己的宽和高 2.onLayout:决定子View的放置位置 3.onTouchEvent[监听动作] 自定

彷QQ5.0侧滑菜单(自定义控件--SlideMenu的实现)

一般的侧滑的实现 ViewGroup  Menu + Content onTouchEvent MOVE:ViewGroup的leftMargin UP:根据显示菜单的高度,决定将其隐藏或者显示 1.Scroller 2.LeftMargin + Thread 彷QQ5.0侧滑菜单的实现,使用另外的一种方法,继承HorizontalScrollView 一.自定义ViewGroup 1.构造方法的选择,获得一些需要用到的值 2.onMeasure 计算子View的宽和高,以及设置自己的宽和高 3

Android QQ5.0侧滑菜单

背景 相信大家用过QQ的人,都会知道QQ有这样的一个功能,那就是他的菜单,感觉就像抽屉一样被拉出来的感觉,感觉很拉风啊,酷酷的,如果你有一种想要把它给弄清楚,想明白,这是一个好东西,你要把它变成自己的,那么现在就是你的机会,也在此恭喜你,你终于初步具有一个向中级工程师迈进的门票了,因为你已经跨出第一步了. 一:效果图,走一走,天下我有 二:实现步骤 1.菜单和内容布局的实现 2.自定义viewgroup 3.给注册监听事件,添加动画效果 三:示例源码 ①菜单布局 <?xml version=&quo

QQ5.0侧滑菜单

QQ5.0侧滑菜单 功能分类:社交    支持平台:Android    运行环境:Eclipse 开发语言:Java    开发工具:Eclipse     源码大小:1.06MB 源码简介 和 QQ 侧滑菜单类似效果,包括缩放.透明度变化.位置变化. 下载地址:http://www.dwz.cn/wYBof 源码运行截图  

【案例分享】仿QQ5.0侧滑菜单ResideMenu

本文由孙国威 原创.如需转载,请注明出处! 为了后续对这个项目进行优化,比如透明度动画.背景图的位移动画,以及性能上的优化. 我把这个项目上传到github上面,请大家随时关注. github地址https://github.com/sunguowei 最近项目要做一个QQ5.0的侧滑菜单效果,和传统的侧滑菜单存在着一些差异.想必大家都已经见识过了. 为了不重复发明轮子,先去github上面搜索了一番. 发现了几个类似的,但是还是有一些不同. 下面是搜索到的类似的开源项目. RESideMenu

【转】仿QQ5.0侧滑菜单ResideMenu

本文由孙国威 原创.如需转载,请注明出处! 原文:http://blog.csdn.net/manoel/article/details/39013095 为了后续对这个项目进行优化,比如透明度动画.背景图的位移动画,以及性能上的优化. 我把这个项目上传到github上面,请大家随时关注. github地址https://github.com/sunguowei 最近项目要做一个QQ5.0的侧滑菜单效果,和传统的侧滑菜单存在着一些差异.想必大家都已经见识过了. 为了不重复发明轮子,先去githu

Android 高仿 QQ5.0 侧滑菜单效果 自定义控件来袭

1.原理分析 首先对比一下我们上篇的实现距离QQ的效果还有多远: 差距还是蛮大的 区别1.QQ的内容区域会伴随菜单的出现而缩小 区别2.QQ的侧滑菜单给人的感觉是隐藏在内容的后面,而不是拖出来的感觉 区别3.QQ的侧滑菜单有一个缩放以及透明度的效果~ 那么我们如何能做到呢: 对于区别1:这个好办,我们可以在滑动的时候,不断的改变内容区域的大小:如何改变呢?我们在菜单出现的整个过程中,不断记录菜单显示的宽度与其总宽度的比值,是个从0到1的过程,然后把0~1转化为1~0.7(假设内容区域缩小至0.7

安卓开发笔记——自定义HorizontalScrollView控件(实现QQ5.0侧滑效果)

对于滑动菜单栏SlidingMenu,大家应该都不陌生,在市场上的一些APP应用里经常可以见到,比如人人网,FaceBook等. 前段时间QQ5.0版本出来后也采用了这种设计风格:(下面是效果图) 之前在GitHub上看到过关于此设计风格的开源项目,它只需要引入对应的类库,就可以定制灵活.各种阴影和渐变以及动画的滑动效果的侧滑菜单. 但作为开发人员,在学习阶段还是建议尽可能的去自己实现,所以今天我不讲此开源项目的使用方式,我们用自定义HorizontalScrollView来实现此效果. 下面先