Android 使用popuwindow仿drawLayout实

2019-03-11  本文已影响0人  When_Young

Android 使用popuwindow仿drawLayout实现侧滑隐藏

背景:

最近公司有一个需求,点击列表项展示列表项详情,我使用popuwindow实现之后公司又要求要实现侧滑返回功能。本着懒的态度我在网上度娘goole一番之后并无头绪。只有撸起袖子自己干。最后功夫不负有心人,最终实现侧滑隐藏,还有很多不足的地方希望大家指正。

优势:

使用popuwindow相比drawLayout更大程度的解耦,方便复用。在任何地方都可以弹出popuwindow

实现思路:

1、先获取popuwindow的触摸事件

2、通过触摸事件改变popuwindow的位置

3、判断滑动位置是否超过阈值,超过就执行消失动画,否者就执行回弹动画

具体代码

1、先获取popuwindow的触摸事件

//先获取 触摸事件
setTouchInterceptor(this);

2、通过触摸事件改变popuwindow的位置

如何改变popuwindow的位置,这个问题我在官方api没有找到对应方式,就通过查看popuwindow的源码找到了灵感,下面是源码一部分,通过查看源码不难发现 mDecorView这个成员变量activity的activity.getWindow().getDecorView()所获取的到的类似,就是popuwindow的根view,

  /** View that handles event dispatch and content transitions. */
  private PopupDecorView mDecorView;

之后就是通过反射获取到这个view

 private View getDecorView() {//获取popudowindow 的根view
        try {
            Field field = PopupWindow.class.getDeclaredField("mDecorView");
            field.setAccessible(true);
            return  (View) field.get(this);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        try {//适配api 19
            Field field = PopupWindow.class.getDeclaredField("mPopupView");
            field.setAccessible(true);
            return  (View) field.get(this);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

之后就是处理触摸事件来改变mDecorView的位置来实现改变popuwindow的显示位置

    public static final int EVENT_INSIDE=0x001;//初始点在范围外
    public static final int EVENT_OUTSIDE=0x002;//初始点在范围内
    public static final int EVENT_END = 0x003;//结束
    public static final int SLIDE_TYPE_VERTICAL = 0x004;//垂直滑动
    public static final int SLIDE_TYPE_HORIZONTAL = 0x005;//水平滑动



    int eventStatu;
    int slideType;

    float eventStartX;
    float eventStartY;
    float viewStartX;
    float viewstartY;


    float damp = 0.7f;
    float slideTypeXThreshold = 5;//判断滑动方向的阈值
    float slideTypeYThreshold = 5;//判断滑动方向的阈值
    float actionThreshold = 0.25f;//判断当滑动整个弹窗的多少时关闭弹窗
    float distance;
    boolean isAnimation=false;
    int allAnimation = 500;//动画执行最大时间

//处理触摸事件
    private boolean onTouchEvent(MotionEvent ev){
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                if(isAnimation){
                    return true;
                }
                //判断按下的点是否在弹窗范围内,如果不是就不处理这个事件了
                float x = ev.getX();
                float y = ev.getY();
                eventStartX = x;
                eventStartY = y;

                //获取弹窗的范围
                float popuStartX = rootView.getX();
                float popuStartY = rootView.getY();
                viewStartX = popuStartX;
                viewstartY = popuStartY;


                float popuEndX = popuStartX+rootView.getWidth();
                float popuEndY = popuStartY+rootView.getHeight();

                if(x>=popuStartX&&x<=popuEndX&&y>=popuStartY&&y<=popuEndY){//按下的点在弹窗返回内需要处理本次触摸事件
                    eventStatu = EVENT_INSIDE;
                }else {
                    eventStatu = EVENT_OUTSIDE;
                }
                slideType = 0;//初始化滑动状态
                distance = 0;
                mDecorView = null;

                break;
            case MotionEvent.ACTION_UP:
                if(isAnimation){
                    return true;
                }
                eventStatu = EVENT_END;
                //判断是否需要关闭弹窗
                if(distance/actionThreshold/damp>=rootView.getWidth()){
                    setDismissAnimation();
                }else {//回弹效果
                    setSpringbackAnimation();
                }


                break;
            case MotionEvent.ACTION_MOVE:
                if(isAnimation){
                    return true;
                }
                if(eventStatu==EVENT_INSIDE){
                    //如果在竖直滑动之后不处理水平滑动了
                    float newX = ev.getX();
                    float relXDistance = newX - eventStartX;//真实位移
                    float newY = ev.getY();
                    float relYDistance = newY - eventStartY;//真实位移

                    if(slideType!=SLIDE_TYPE_HORIZONTAL&&Math.abs(relYDistance)>DensityUtils.dip2px(context,slideTypeYThreshold)){//如果大于阈值
                        slideType = SLIDE_TYPE_VERTICAL;
                    }

                    if(slideType!=SLIDE_TYPE_VERTICAL&&Math.abs(relXDistance)>DensityUtils.dip2px(context,slideTypeXThreshold)){//如果大于阈值
                        slideType = SLIDE_TYPE_HORIZONTAL;
                    }

                    if(slideType==SLIDE_TYPE_VERTICAL){//如果是竖直方向就继续处理了
                        return false;
                    }
                    if(slideType==SLIDE_TYPE_HORIZONTAL){
                        if(distance<0){
                            return true;
                        }

                        distance = relXDistance*damp;//乘以阻尼之后的距离
                        setMDecorView(viewStartX+distance);
                        return true;
                    }


                }
                break;
        }

        return false;
    }


    private void setMDecorView(float x) {
        //改变view的位置
        if(mDecorView==null){
            mDecorView = getDecorView();
        }

        if(mDecorView!=null){
            if(x>=viewStartX){//避免移动位置越过popuwindow的初始位置
                mDecorView.setX(x);
            }
        }
    }



最后就是在触摸事件结束时判断是否滑动距离超过阈值,如果超过就执行隐藏动画,否者就执行回弹

 //判断是否需要关闭弹窗
                if(distance/actionThreshold/damp>=rootView.getWidth()){
                    setDismissAnimation();
                }else {//回弹效果
                    setSpringbackAnimation();
                }

下面是消失动画和回弹动画

   private void setDismissAnimation() {
        //改变view的位置
        if(mDecorView==null){
            mDecorView = getDecorView();
        }

        if(mDecorView==null){
            return;
        }


        float width =Utils.getScreenWidth(context);
        float offSet= width-mDecorView.getX();
        if(offSet<0){
            return;
        }
        int duration = (int) (300*(offSet/(mDecorView.getWidth()*(1-actionThreshold))));
        ObjectAnimator animator = ObjectAnimator.ofFloat(mDecorView,"translationX",mDecorView.getX(),width);
        animator.setDuration(duration);
        animator.start();
        animator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isAnimation=false;
                dismiss();
            }

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                isAnimation=true;

            }
        });
    }
private void setSpringbackAnimation() {
        //改变view的位置
        if(mDecorView==null){
            mDecorView = getDecorView();
        }

        if(mDecorView==null){
            return;
        }
        float offSet= mDecorView.getX()-viewStartX;
        if(offSet<0){
            return;
        }
        int duration = (int) (allAnimation*(offSet/(mDecorView.getWidth()*actionThreshold)));
        ObjectAnimator animator = ObjectAnimator.ofFloat(mDecorView,"translationX",mDecorView.getX(),viewStartX);
        animator.setDuration(duration);
        animator.start();
        animator.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                isAnimation=false;
            }

            @Override
            public void onAnimationStart(Animator animation) {
                super.onAnimationStart(animation);
                isAnimation=true;
            }
        });
    }

到此就完全完成了

上一篇下一篇

猜你喜欢

热点阅读