自定义抽屉—QQ特效

这个我是根据黑马视频做的,用来总结一下。抽屉用到的很多,下面写的比较麻烦,需要读者有耐心的看完。写的不对的地方请提出来。
抽屉我继承的是FrameLayout,因为他有测量的过程,省略了我手动测量。它最重要的就是实现ViewDragHelper的回调方法。文章最后有下载地址。
首先,我们在自定义的View的构造方法里面初始化ViewDragHelper,他是单例模式创建的,并不是new出来的。定义成全局的。
`viewDragHelper = ViewDragHelper.create(this, mCallBack);`
然后就是重写他的回调方法。回调方法,我都是按他们调用的顺序排好了。
            @Override
            public boolean tryCaptureView(View child, int pointerId) {
                //尝试拖拽当前View,如果不能拖拽,就不执行之后的方法
                return true;
            }
child是当前触摸的View,pointerId是区分多点触摸的Id(没什么用)。注释里面写的有根据返回值判断当前View能不能被拖拽。如果是true,就是不论那个View都能被拖拽。抽屉一般有两个界面。这里就是在一个XML里面写两个平级的Layout就可以了,就是两个界面。比方说,我现在有两个Layout,一个是mMainContent,另一个是mLeftContent。这里如果写成return child==mLeftContent,那就是只有mLeftContent布局能够被拖拽。这里我们就写成return true。后面会有说明。因为你限制只能拖拽mMainContent,当你拖拽mLeftContent的时候,你还想让mMainContent跟着移动。这个时候就需要测量位置,但是如果mLeftContent不能被拖拽,那他就不会执行后面的测量方法。你这里让mLeftContent可以被拖拽,在你拖拽的时候,你重绘界面就可以了,把mLeftContent的位置写死,他就不会移动了,同时也会走后面的测量方法,这样就达到了我们的目的。
            //当View被捕获的时候调用
            @Override
            public void onViewCaptured(View capturedChild, int activePointerId) {
                super.onViewCaptured(capturedChild, activePointerId);
            }
这个方法是当View被捕获的时候调用,前面一个方法是尝试捕获。如果,你正在尝试捕获的View不能被拖拽,那他就获取不到当前View。
            //获取当前View可以横向拖拽的范围
            @Override
            public int getViewHorizontalDragRange(View child) {
                //可拖拽的范围,但是不对拖拽进行限制,仅仅是根据可拖拽的范围计算动画的时长
                return mRange;
            }
这个方法注释里面有,你拖拽的时候,不可能拖拽到屏幕外面去,如果出现这样的结果,那就是你代码有bug了。这个方法就是避免这样的情况产生。这个范围我写的是0~0.6f*screenWidth。0到0.6倍的当前屏幕宽度。这是横向的,既然有横向的就一定有纵向的。没必要写纵向的。mRange是怎么来的呢?下面这个方法。
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //在onMeasure方法之后调用,并且只有在当前尺寸与之前测量的不相同时调用
        mHeight = getMeasuredHeight();  //获取测量的屏幕宽度
        mWidth = getMeasuredWidth();    //获取测量的屏幕高度
        mRange = (int) (mWidth * 0.6f);   //获取可拖拽的横向范围
    }
onMeasure方法大家可能都知道,测量宽高的方法,这个方法在onMeasure方法之后调用,并且只有在当前尺寸与之前测量的不相同时调用,就这样,就获取到了mRange。
那最大移动的范围出来了,我们要怎么移动呢?实现下面这个方法
            @Override
            public int clampViewPositionHorizontal(View child, int left, int dx) {
                //oldLeft:child.getLeft()
                //left=oldLeft+dx
                if (child == mMainContent) {
                    left = fixLeft(left);
                }
                return left;
            }
这个方法是:根据建议值修正将要移动到的(横向)位置,此时还没有发生移动。这个方法也有纵向的。child是当前拖拽的View,left是新位置的建议值。dx是位置的变化量。left=oldLeft+dx。oldLeft:child.getLeft()。根据范围修正左边值,我把left的方法提取出来了,后面还有位置用的到。    
private int fixLeft(int left) {
        if (left < 0) {
            return 0;
        } else if (left > mRange) {
            return mRange;
        } else {
            return left;
        }
    }

前面这个方法是还没有移动的时候调用,这个时候屏幕还不会动,当View发生移动的时候调用下面这个方法。

 @Override
            public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
                super.onViewPositionChanged(changedView, left, top, dx, dy);
                int newLeft = left;
                if (changedView == mLeftContent) {
                    newLeft = mMainContent.getLeft() + dx;
                }
                newLeft = fixLeft(newLeft); //要保证移动的距离不能超过mRange
                if (changedView == mLeftContent) {
                    //mLeftContent保持不变,动态移动mMainContent
                    mLeftContent.layout(0, 0, 0 + mWidth, 0 + mHeight);
                    mMainContent.layout(newLeft, 0, newLeft + mWidth, 0 + mHeight);
                }
                //更新状态,执行动画
                dispatchDragEvent(newLeft);
                //为了兼容低版本,在每次修改值之后,都要进行重绘
                invalidate();
            }

移动之后还有一个松开点,这个就是调用下面这个方法。

/**
             * 4.当View被释放时,处理的事情(执行动画)
             *
             * @param releasedChild 被释放的View
             * @param xvel          水平方向的速度,向右为正
             * @param yvel          垂直方向的速度,向下为正
             */
            @Override
            public void onViewReleased(View releasedChild, float xvel, float yvel) {
                super.onViewReleased(releasedChild, xvel, yvel);
                if (xvel == 0 && mMainContent.getLeft() > mRange / 2.0f) {
                    open();
                } else if (xvel > 0) {
                    open();
                } else {
                    close();
                }
            }
我们需要有一个平滑的移动过程,而不是瞬移。所以,我们要加一个平滑的动画。后面的我也不知道该怎么解释。文字表达能力有限,大家就认真的看完就好了。就是动画效果。是用的github上面一个大牛整理好的。
private void animView(float percent) {
        // >1.左面板:缩放动画,平移动画,透明动画
//        mLeftContent.setScaleX(0.5f + 0.5f * percent);
//        mLeftContent.setScaleY(0.5f + 0.5f * percent);
        //缩放动画
        ViewHelper.setScaleX(mLeftContent, evaluate(percent, 0.5f, 1.0f));
        ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent);
        //平移动画
        ViewHelper.setTranslationX(mLeftContent, evaluate(percent, -mWidth / 2.0f, 0));
        //透明度动画
        ViewHelper.setAlpha(mLeftContent, evaluate(percent, 0.5f, 1.0f));
        // >2.主面板:缩放动画1.0->0.8
        ViewHelper.setScaleX(mMainContent, evaluate(percent, 1.0f, 0.8f));
        ViewHelper.setScaleY(mMainContent, evaluate(percent, 1.0f, 0.8f));
        // >3.背景:亮度变化(颜色变化)
//        getBackground().setColorFilter((Integer) evaluateColor(percent, Color.YELLOW,Color.BLUE), PorterDuff.Mode.SRC_OVER);
        getBackground().setColorFilter((Integer) evaluateColor(percent, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);
    }
  /**
     * 颜色变化过度
     *
     * @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 percentage 百分比
     * @param startValue 开始值
     * @param endVlaue   结束值
     * @return
     */
    public Float evaluate(Float percentage, Number startValue, Number endVlaue) {
        float startFloat = startValue.floatValue();
        return startFloat + percentage * (endVlaue.floatValue() - startFloat);
    }

上面这个几个方法就是这样写死,会用就行了。这个我就是copy官方文档里面的。下面这个也是比较重要的,但是不难。

//b.传递触摸事件

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //传递给viewDragHelper,是否需要拦截触摸事件
        return viewDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        try {
            //处理触摸事件,多点触摸有问题
            viewDragHelper.processTouchEvent(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //返回true,持续接收事件
        return true;
    }

    /**
     * 当xml添加完成时调用
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() < 2) {
            throw new IllegalStateException("Your ViewGroup must have 2 children at least.");
        }
        if (!(getChildAt(0) instanceof ViewGroup && getChildAt(1) instanceof ViewGroup)) {
            throw new IllegalArgumentException("Your children must be instanceof ViewGroup.");
        }
        mLeftContent = (ViewGroup) getChildAt(0);
        mMainContent = (ViewGroup) getChildAt(1);
    }

持续动画的代码

 //持续动画
    @Override
    public void computeScroll() {
        super.computeScroll();
        if (viewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

算了,不说了,基本上都说了。下面给出下载项目的链接。下载了自己认真瞅瞅就好了。
项目链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海晨忆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值