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

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;
            }
        });
    }

到此就完全完成了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,096评论 1 32
  • 冬月离分后,频频夜梦君。 身沾归化雪,心忆故乡云。 怎借南鹏翼,安凭北雁群。 怀归终有日,携手谒将军。
    何处拾珠阅读 169评论 0 2
  • 咏梅两首 戊戌年腊月初四,又读岳武穆公《满江红》,悲恸之余,感韶光易逝,年华不再,余心中怅然。遂有此作。 一 炉红...
    十二读书会阅读 795评论 15 12
  • 我们和公婆住一起已经四年多了,在这四年里,我和婆婆从来没有红过脸。平常出去逛街的时候,人家总是问我婆婆,这是你闺女...
    微风008阅读 555评论 8 6