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

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

一、效果分析

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

@Override
public boolean tryCaptureView(View child, int pointerId) {

    return true;
}


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

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

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

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

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


4、手指释放

@Override
public 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事件拦截和处理

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {

    return mViewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    mViewDragHelper.processTouchEvent(event);
    return true;
}

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

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

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

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

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

leftMenu左菜单
contentMenu 主菜单

@Override
protected 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方法中得到它测量的宽度,并且计算出能够滑动的最大范围

@Override
protected 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左边,可以在此进行返回控制
@Override
public 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的位置改变都会回掉这里
@Override
public 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基本一样用法)

//释放
@Override
public 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();
}
@Override
public void computeScroll() {
    super.computeScroll();
    if(mViewDragHelper.continueSettling(true)){
        postInvalidate();
    }
}

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


@Override
public 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如果可以点击,在水平方向要返回一个滑动的范围

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

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

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

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

//位置改变
@Override
public 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();
}
恢复坐标

@Override
protected 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值