ViewDragHelper 自定义ViewGroup实现QQ5.0侧滑效果

来源:互联网 发布:红辣椒电影数据分析 编辑:程序博客网 时间:2024/04/25 13:13

前言:利用ViewDragHelper 实现一个ViewGroup,学习巩固一下ViewDragHelper的相关知识。先看下效果

一、效果分析

1、QQ5.0侧滑效果,从左边滑动的时候,主页内容移动并且缩小,左边内容放大且平移(主要实现这个效果)
二、首先对实例化ViewDragHelper时候必须要传入的ViewDragHelper.Callback抽象类几个方法的了解
1、在拖动的时候这个child是否可以拖动,如果可以拖动则返回true,否则返回false

@Overridepublic boolean tryCaptureView(View child, int pointerId) {    return true;}


2、当拖动的view位置发生变化的时候都会回调这里

/
//位置改变@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
}

3、水平和垂直方向移动的 坐标,如果返回0,则表示不会移动

@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {        return left;}
父类默认返回0 ,所以如果不重写的话垂直方向不会有拖动效果

@Overridepublic int clampViewPositionVertical(View child, int top, int dy) {    return super.clampViewPositionVertical(child, top, dy);}


4、手指释放

@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {   }

现在开始我们自定义ViewGroup,首先自定义一个类MySlideMeun继承FrameLayout,FrameLayout包含两个LineLayout,第一个作为左边的菜单,第二个作为主页的菜单

我们要利用ViewDragHelper,就肯定要将onTouch事件交给它来处理,包括事件拦截和事件处理。所以现在在构造方法中实例化一个ViewDragHelper,如果不用包含敏感度的构造函数,就会使用默认的敏感度(简单理解也就是手指滑动多远才算有效)

mViewDragHelper = ViewDragHelper.create(this, new ViewDragHelper.Callback() {    //如果当前拖拽的view可以被拖动的话,返回true否则false    @Override    public boolean tryCaptureView(View child, int pointerId) {        return true;    }
然后MySlideMeun交出onTouch事件拦截和处理

@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {    return mViewDragHelper.shouldInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {    mViewDragHelper.processTouchEvent(event);    return true;}

接着重写水平方向的移动,

@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {        return left;}

目前为止,ViewDragHelper就可拖动MySlideMeun中的所有子View。

现在把需要的一些对象和参数得到一下

拿到两个linelayout,作为我们滑动的菜单

leftMenu左菜单
contentMenu 主菜单

@Overrideprotected void onFinishInflate() {    super.onFinishInflate();    try {        leftMenu = (LinearLayout) getChildAt(0);        contentMenu = (LinearLayout) getChildAt(1);    } catch (Exception e) {        e.printStackTrace();    }}
水平滑动的范围默认设置为控件宽度的0.8

//控制滑动的范围系数private float scope = 0.8f;
在MySlideMeun 的onSizeChanged方法中得到它测量的宽度,并且计算出能够滑动的最大范围

@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    widthScope = (int) (getMeasuredWidth()*scope);}

下一阶段:上面的步骤完成之后,就可以滑动了,现在要处理滑动的范围问题,和当我们拖动左边菜单的时候,主菜单也需要滑动相应的距离,下面就来解决一下这两个问题。

首先控制主菜单的范围,主菜单left坐标的应该是从0 到 widthScope 

//child,在水平方向移动的dx,left,要移动到的left左边,可以在此进行返回控制@Overridepublic int clampViewPositionHorizontal(View child, int left, int dx) {    //如果当前拖动的是contentMeun,则修正它移动的坐标    left=child==contentMenu?fixHorX(left):left;    return left;}
/** * 修正x左边,contentMenu 水平移动的范围 * @param x */public int fixHorX(int x){    x=x<0?0:x;    x=x>widthScope?widthScope:x;    return x;}

  当左边菜单在发生拖动的时候,移动主菜单的位置,这个时候在拖动左边菜单的时候主菜单就会跟着移动

//所有view的位置改变都会回掉这里@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {    //如果拖动的是leftMenu,位置还原到0,0,并且把contentMeun的位置移动    if(changedView==leftMenu){        leftMenu.layout(0,0,leftMenu.getWidth(),leftMenu.getHeight());        left =fixHorX(contentMenu.getLeft()+dx);
        contentMenu.layout(left,0,left+contentMenu.getWidth(),contentMenu.getHeight());
} }

现在为止,基本实现了拖动的功能,接着需要在手指释放的时候,将主菜单打开或者关闭,我们通过判断释放时候主菜单的left坐标,如果大于滑动范围的一半则打开,否则就关闭它,在打开和关闭的方法中,利用ViewDragHelper的实现慢慢滑动到打开和关闭状态(和scroll基本一样用法)

//释放@Overridepublic void onViewReleased(View releasedChild, float xvel, float yvel) {    if(contentMenu.getLeft()>=widthScope/2){        open();    }else{        close();    }}

public void open(){    //contentMenu从当前坐标移动到最右边    mViewDragHelper.smoothSlideViewTo(contentMenu,widthScope,0);    postInvalidate();    Log.i(Tag,"widthScope:"+widthScope);}public void close(){    mViewDragHelper.smoothSlideViewTo(contentMenu,0,0);    postInvalidate();}
@Overridepublic void computeScroll() {    super.computeScroll();    if(mViewDragHelper.continueSettling(true)){        postInvalidate();    }}

侧滑菜单基本就已经完成了,然后根据每次主菜单left坐标的变化值,对它和左边菜单进行属性动画,计算left坐标除以滑动返回,算出现在已经滑动的百分比,也就是从0.0到1.0的值,根据这个值再利用float估值器FloatEvaluator,就可以很好控制属性动画


@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {    //如果拖动的是leftMenu,位置还原到0,0,并且把contentMeun的位置移动    if(changedView==leftMenu){        leftMenu.layout(0,0,leftMenu.getWidth(),leftMenu.getHeight());        left =fixHorX(contentMenu.getLeft()+dx);    }else{        left=fixHorX(left);    }    currentLeft_ContentMeun = left;    contentMenu.layout(left,0,left+contentMenu.getWidth(),contentMenu.getHeight());    float percent = currentLeft_ContentMeun*1.0f/widthScope*1.0f;    Log.i(Tag,"percent:"+percent+"currentLeft_ContentMeun:"+currentLeft_ContentMeun            +"widthScope"+widthScope);    doAnim(percent);   

public void doAnim(float precent){    //contentMeun,缩小 precent 0,1  缩小 1,0.8    contentMenu.setScaleY(evaluate(precent,1f,0.8f));    //leftMenu x坐标,从-width/2 到0,大小,从0.5 到1    leftMenu.setTranslationX(evaluate(precent,-leftMenu.getWidth()/2,0));    leftMenu.setScaleX(evaluate(precent,0.5,1f));    leftMenu.setScaleY(evaluate(precent,0.5,1f));}

写到这里基本完成90%,最后还有两个问题需要解决,主菜单中的子view如果可以点击,在水平方向要返回一个滑动的范围

@Overridepublic int getViewHorizontalDragRange(View child) {    return widthScope;}

那么现在任何时候都可以获取到点击事件,比如有个listview,就可以在主菜单非关闭的时候还是可以滑动,这样体验不太好,需要自定义ViewGroup,在MySlideMenu处于非关闭状态的时候,自定义ViewGroup都会拦截所有事件不会往下传递。

最后处理一个调试时候可能不会出现的bug,在华为手机锁屏再打开后,拖动的view会回到原来未拖动的地方,所以我们需要每次记录下主菜单的left坐标

然后在MySlideMenu执行onlayout方法的时候,重新恢复保存的主菜单的位置

//位置改变@Overridepublic void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {    //如果拖动的是leftMenu,位置还原到0,0,并且把contentMeun的位置移动    if(changedView==leftMenu){        leftMenu.layout(0,0,leftMenu.getWidth(),leftMenu.getHeight());        left =fixHorX(contentMenu.getLeft()+dx);    }else{        left=fixHorX(left);    }    currentLeft_ContentMeun = left;//保存坐标    contentMenu.layout(left,0,left+contentMenu.getWidth(),contentMenu.getHeight());    float percent = currentLeft_ContentMeun*1.0f/widthScope*1.0f;    Log.i(Tag,"percent:"+percent+"currentLeft_ContentMeun:"+currentLeft_ContentMeun            +"widthScope"+widthScope);    doAnim(percent);    updateStatus();}
恢复坐标

@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {    super.onLayout(changed, left, top, right, bottom);    //防止在重新刷新页面的时候出现content回到0,0,恢复contentMenu的位置    contentMenu.layout(currentLeft_ContentMeun,0,contentMenu.getWidth(),contentMenu.getHeight());}

最后一点点就是状态的监听了,添加接口和泛型没有什么好说的,在源码中。。。

源码下载















1 0
原创粉丝点击