ViewDragHelpe的简单使用-QQ5.0菜单特效

效果还是很好看的,虽然现在QQ的侧滑已经变样了,但是对于ViewDragHelper入门来说,这是不错的案例!

在主面板任意位置向右滑动 显示左侧菜单栏,主面板也随左菜单的放大而缩小,底部背景由暗变亮,

Android studioQQ侧滑 qq侧滑菜单如何设置_ide

Android studioQQ侧滑 qq侧滑菜单如何设置_DragHelper_02

Android studioQQ侧滑 qq侧滑菜单如何设置_android_03

Android studioQQ侧滑 qq侧滑菜单如何设置_Android studioQQ侧滑_04

创建此类有三个步骤,接下来一起看看如何用代码实现吧:

首先定义布局文件

/**
 * 左右两个布局都在一个布局文件中 根节点为自定义的DragLayout 并设置背景图片
 * 左为相对布局,右为自定义的相对布局(自定义是为了处理与页面子控件滑动冲突)
 * Created by shang on 2015/12/3.
 */
 //左菜单布局
<RelativeLayout
        android:id="@+id/ll_left"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="50dp"
        android:paddingLeft="10dp"
        android:paddingRight="50dp"
        android:paddingTop="50dp">
        <ImageView
            android:id="@+id/img1"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:clickable="true"
            android:src="@mipmap/qq"></ImageView>
        <TextView
            android:id="@+id/qqName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="15dp"
            android:layout_toRightOf="@id/img1"
            android:text="用户名"
            android:textColor="#ffffff"
            android:textSize="20sp" />
        <TextView
            android:id="@+id/tv_explain"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/qqName"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="5dp"
            android:layout_toRightOf="@id/img1"
            android:text="“个性签名.......”"
            android:textColor="#ffffff"
            android:textSize="10sp" />
    </RelativeLayout>
/**
 * 自定义主面板布局(右) 主面板XML略。
 * Created by shang on 2015/12/3.
 */
public class MyRelativeLayout extends RelativeLayout {
    private DragLayout draglayout;


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

    public MyRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    //重写下面两个触摸事件是为了防止在划出菜单是还可以点击主面板的子控件,因此,父控件拦截触摸事件并消耗掉
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (draglayout != null && draglayout.getStatus() != DragLayout.Status.Close) {
            return true;
        } else {
            return super.onInterceptTouchEvent(ev);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (draglayout != null && draglayout.getStatus() != DragLayout.Status.Close) {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                draglayout.close();
            }
            return true;
        } else {
            return super.onTouchEvent(event);
        }
    }

    public void setDraglayout(DragLayout draglayout) {
        this.draglayout = draglayout;
    }

}

重点来了!认真阅读方法及参数的注释!

/**
 * ------ViewDragHelper的使用------
 * 自定义侧滑菜单
 * Created by shang on 2015/12/3.
 */
public class DragLayout extends FrameLayout {
    private ViewDragHelper mHelper;
    private ViewGroup mLeftContent;
    private ViewGroup mMainContent;
    private int mHeight;
    private int mWidth;
    private int mRange;
    //定义面板操作的状态
    public static enum Status {
        Close, Open, Draging
    }

    private Status status = Status.Close;
    private OnDragChangeListener mOnDragChangeListener;
    /**
    *定义回调函数,供外部实时调用
    */
    public interface OnDragChangeListener {
        public void onOpen();

        public void onDraging(float percent);

        public void onClose();
    }

    public void setOnDragChangeListener(OnDragChangeListener mOnDragChangeListener) {
        this.mOnDragChangeListener = mOnDragChangeListener;
    }

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

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

    public DragLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        /* 
        *  第一步:在构造函数中创建 ViewDragHelper 辅助类
        */
        mHelper = ViewDragHelper.create(this, 0.1f, callback);
    }
    /**
     * 第二步:转交触摸事件给ViewDragHelper处理
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            mHelper.processTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 第三步:创建回调函数,用于初始化辅助类,想实现QQ的效果,必须重写其中的五个方法!
     */
    ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        /**
         *  1.返回值,决定了child是否可以被拖拽
         * @param child 被滑动的子View
         * @param pointerId 多点触摸的手指Id
         * @return true:可以被拖拽
         */
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        /**
         * 2.返回拖拽的范围,返回一个>0的值,决定了动画的执行时长,水平方向是否可以被滑动
         * @param child 子控件
         * @return mRange 范围
         */
        @Override
        public int getViewHorizontalDragRange(View child) {
            return mRange;
        }

        /**
         * 3.修正子View水平方向的位置,此时还没有发生真正的移动
         * @param child 被拖拽的子View
         * @param left 建议移动到的位置
         * @param dx 与旧值的偏移量
         * @return left 移动到的位置
         */
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (child == mMainContent) {
                left = fixLeft(left);
            }
            return left;
        }

        /**
         * 4.控件位置变化时调用 :伴随动画、状态更新、事件回调
         * @param changedView 发生改变的子View
         * @param left  水平方向最新的位置
         * @param top   ..
         * @param dx    刚刚发生的偏移量
         * @param dy    ..
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            //如果移动的是左面板,就设置回原位,并将偏移量作用在主面板(右) .layout(l,t,r,b);
            if (changedView == mLeftContent) {
                mLeftContent.layout(0, 0, mWidth, mHeight);
                int newLeft = mMainContent.getLeft() + dx;
                //修正左边值
                newLeft = fixLeft(newLeft);
                //主面板的宽度应为newLeft + mWidth
                mMainContent.layout(newLeft, 0, newLeft + mWidth, mHeight);
            }
            /** 添加伴随动画 **/
            dispatchDragEvent();
            //兼容低版本,手动重绘
            invalidate();
        }

        /**
         * 5.松手之后调用的方法:结束动画
         * @param releasedChild 被释放的子View
         * @param xvel X轴速度
         * @param yvel  Y轴速度
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            //打开、关闭主(右)面板
            if (xvel >= 0 && mMainContent.getLeft() > mRange * 0.2f) {
                open();
            } else {
                close();
            }
        }
    };
 /**
     * 填充View完毕时调用的方法
     * 用于检查错误和得到子View对象
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() < 2) {
            throw new IllegalStateException("错误!至少有两个子控件!");
        }
        if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) {
            throw new IllegalArgumentException("子控件类型错误!");
        }
        mLeftContent = (ViewGroup) getChildAt(0);
        mMainContent = (ViewGroup) getChildAt(1);
    } 
    /**
     * 当控件尺寸变化时调用
     * onMeasure执行完调用此方法
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = getMeasuredWidth();
        mHeight = getMeasuredHeight();
        //计算拖拽的范围
        mRange = (int) (mWidth * 0.6f);
    }

    private void dispatchDragEvent() {
        //得到偏移的百分比
        float percent = mMainContent.getLeft() * 1.0f / mRange;
        if (mOnDragChangeListener != null) {
            mOnDragChangeListener.onDraging(percent);
        }
        //更新状态
        Status lastStatus = status;
        status = updateStatus(percent);
        if (lastStatus != status && mOnDragChangeListener != null) {
            if (status == Status.Close) {
                mOnDragChangeListener.onClose();
            } else if (status == Status.Open) {
                mOnDragChangeListener.onOpen();
            }
        }
        // 执行动画
        animViews(percent);

    }

    private Status updateStatus(float percent) {
        if (percent == 0) {
            return Status.Close;
        } else if (percent == 1) {
            return Status.Open;
        }
        return Status.Draging;

    }

    /**
     * 伴随动画 渐变、缩放、平移
     */
    private void animViews(float percent) {
        //左面板:缩放 percent * 0.5 + 0.5     0.5-->1.0
//        mLeftContent.setScaleX(percent * 0.5f + 0.5f);
//        mLeftContent.setScaleY(percent * 0.5f + 0.5f);
        /** 兼容低版本 **/
        ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
        ViewHelper.setScaleY(mLeftContent, evaluate(percent, 0.5f, 1.0f));

        //左面板:平移 -mWidth / 2.0f -->0
        ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
        //左面板:渐变 0.2f --> 1.0f
        ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.2f, 1.0f));
        //主面板:缩放 1.0f - 0.8f
        ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
        ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
        //背景:亮度变化
        getBackground().setColorFilter((Integer) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
    }

    /**
     * ArgbEvaluator 中复用方法 --颜色过渡
     *
     * @param fraction
     * @param startValue
     * @param endValue
     * @return
     */
    public Object evaluateColor(float fraction, Object startValue, Object 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))));
    }

    /**
     * 复用类型估值器的方法 得到平移的数值
     *
     * @param fraction
     * @param startValue
     * @param endValue
     * @return
     */
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }

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

    private void open(boolean isSmooth) {
        int finalLeft = mRange;
        if (isSmooth) {
            if (mHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) {
                //重绘界面
                //invalidate();//此方法会漏帧!不建议使用
                //建议:重绘子View所在的容器
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, mHeight);
        }
        this.status = Status.Open;
    }

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

    private void close(boolean isSmooth) {
        int finalLeft = 0;

        if (isSmooth) {
            if (mHelper.smoothSlideViewTo(mMainContent, finalLeft, 0)) {
                //重绘界面
                //invalidate();//此方法会漏帧!不建议使用
                //建议:重绘子View所在的容器
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainContent.layout(finalLeft, 0, finalLeft + mWidth, mHeight);
        }
        this.status = Status.Close;
    }


    /**
     * 每次绘画之前调用
     */
    @Override
    public void computeScroll() {
        super.computeScroll();
        //判断是否需要画下一帧动画,传递true
        if (mHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    private int fixLeft(int left) {
        if (left < 0) {
            return 0;
        } else if (left > mRange) {
            return mRange;
        }
        return left;
    }

    public Status getStatus() {
        return status;
    }

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

}

最后!在外部(例如:MainActivity)中就可以通过回调函数控制了

//外部调用
 dl = (DragLayout) findViewById(R.id.dl);
        dl.setOnDragChangeListener(new DragLayout.OnDragChangeListener() {
            @Override
            public void onOpen() {
            }

            @Override
            public void onDraging(float percent) {
                ViewHelper.setAlpha(toimg1, 1 - percent);
            }

            @Override
            public void onClose() {
                ObjectAnimator oa = ObjectAnimator.ofFloat(toimg1, "translationX", 15f);
                oa.setInterpolator(new CycleInterpolator(4));
                oa.setDuration(500);
                oa.start();

            }
        });

以上就是全部的代码,直接复用就OK,是不是很简单呢~