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

来源:互联网 发布:北外网络学历费用 编辑:程序博客网 时间:2024/05/05 02:10

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>

Demo下载:http://files.cnblogs.com/files/liujingg/DragLayoutDemo.rar


2 0
原创粉丝点击