自定义抽屉—QQ特效

来源:互联网 发布:java笔试基础题 编辑:程序博客网 时间:2024/05/17 21:50
这个我是根据黑马视频做的,用来总结一下。抽屉用到的很多,下面写的比较麻烦,需要读者有耐心的看完。写的不对的地方请提出来。抽屉我继承的是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);        }    }

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

0 0