zoukankan      html  css  js  c++  java
  • 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实现方式最为复杂,但灵活性也更高,可以应对各种需求,毕竟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下载:https://files.cnblogs.com/files/liujingg/DragLayoutDemo.rar

  • 相关阅读:
    innobackupex备份命令输出
    Percona XtraBackup原理详解
    MongoDB性能分析工具mongostat
    MongoDB查看当前连接数
    事务、拦截器
    HttpServletResponse和HttpServletRequest的简单实用
    Maven环境配置
    SQL Server 时间戳与时间格式互相转换
    虚拟机、云主机、VPS 三者之间的区别
    Elasticsearch 空值过滤
  • 原文地址:https://www.cnblogs.com/liujingg/p/4694903.html
Copyright © 2011-2022 走看看