欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮

程序员文章站 2023-02-26 14:26:15
前言 之前写过屏蔽系统导航栏功能的文章,具体可看 "Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar" 在某些特殊定制的版本中要求完全去掉导航栏,那么当用户点进一些系统自带的应用界面如设置、联系人等,就没法退出了,虽然可以在 ......

前言

之前写过屏蔽系统导航栏功能的文章,具体可看android6.0 源码修改之屏蔽导航栏虚拟按键(home和recentapp)/动态显示和隐藏navigationbar

在某些特殊定制的版本中要求完全去掉导航栏,那么当用户点进一些系统自带的应用界面如设置、联系人等,就没法退出了,虽然可以在actionbar中添加back按钮,但总不能每一个app都去添加吧。所以灵机一动我们就给系统添加一个全屏可拖拽的浮窗按钮,点击的时候处理返回键的逻辑。它大概长这样(审美可能丑了点,你们可以*发挥)

Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮
图1 最终效果图

思路分析

  1. 通过分析之前的navigationbar代码,发现系统是通过windowmanager添加view的方式来实现,此处我们也可以模拟这种方法来添加
  2. 添加悬浮窗以后监听触摸事件,跟随手指移动重新修改view的layoutparam
  3. 松手后获取当前x坐标,小于屏幕width的一半则平移归位至屏幕左边
  4. 添加系统的返回按键功能

一、添加悬浮窗

private void showfloatingwindow() {
    displaymetrics outmetrics = new displaymetrics();
    mwindowmanager.getdefaultdisplay().getmetrics(outmetrics);
    screenwidth = outmetrics.widthpixels;
    screenheight = outmetrics.heightpixels;

    layoutparams = new windowmanager.layoutparams();
    layoutparams.type = windowmanager.layoutparams.type_phone;
    layoutparams.format = pixelformat.rgba_8888;
    layoutparams.gravity = gravity.left | gravity.top;
    layoutparams.flags = windowmanager.layoutparams.flag_not_touch_modal | windowmanager.layoutparams.flag_not_focusable;
    layoutparams.width = 100;
    layoutparams.height = 100;
    layoutparams.x = 200;
    layoutparams.y = 200;

    button = new imagebutton(mcontext);
    button.setbackground(mcontext.getresources().getdrawable(r.drawable.fab_background));//系统通讯录里的蓝色圆形图标
    button.setimageresource(r.drawable.ic_sysbar_back);//系统本身的back图标
    mwindowmanager.addview(button, layoutparams);
    isshowfloatingview = true;
}

代码很简单,就是通过windowmanager添加一个imagebutton,宽高都是100的,位置在屏幕左上角为原点的200,200。需要注意的是因为我们是在源码里添加,而且是m的版本,所以type为windowmanager.layoutparams.type_phone。如果是在普通的app里注意事项可参考

二、添加触摸事件监听

button.setontouchlistener(new floatingontouchlistener());

private class floatingontouchlistener implements view.ontouchlistener {
    private int lastx;
    private int lasty;

    @override
    public boolean ontouch(view view, motionevent event) {
        switch (event.getaction()) {
            case motionevent.action_down:
                isdrag = false;
                lastx = (int) event.getrawx();
                lasty = (int) event.getrawy();
                break;

            case motionevent.action_move:
                isdrag = true;
                int nowx = (int) event.getrawx();
                int nowy = (int) event.getrawy();
                int movedx = nowx - lastx;
                int movedy = nowy - lasty;
                lastx = nowx;
                lasty = nowy;
                layoutparams.x = layoutparams.x + movedx;
                layoutparams.y = layoutparams.y + movedy;
                //获取当前手指移动的x和y,通过updateviewlayout方法将改变后的x和y设置给button
                mwindowmanager.updateviewlayout(view, layoutparams);
                break;

            case motionevent.action_up:
                if (isdrag) {
                    log("lastx=" + lastx + "  screenwidth=" + screenwidth);
                    //手指抬起时,判断是需要滑动到屏幕左边还是屏幕右边
                    if (lastx >= screenwidth / 2) {
                        setanimation(view, lastx, screenwidth);
                    } else {
                        setanimation(view, -lastx, 0);
                    }
                }
                break;
        }
        //返回true则消费事件,返回false则传递事件,此处特殊处理是为了和点击事件区分
        return isdrag || view.ontouchevent(event);
    }

三、添加抬起滑动归位动画

private void setanimation(final view view, int fromx, int tox) {
        final valueanimator animator = valueanimator.ofint(fromx, tox);
        if (math.abs(fromx) < screenwidth / 4 || fromx > screenwidth * 3 / 4)
            animator.setduration(300);
        else
            animator.setduration(600);

        animator.setinterpolator(new linearinterpolator());
        animator.addlistener(new animator.animatorlistener() {
            @override
            public void onanimationstart(animator animation) {}
            @override
            public void onanimationend(animator animation) {
                log("onanimationend=");
                saveprevalue(layoutparams.x, layoutparams.y);
            }
            @override
            public void onanimationcancel(animator animation) {}
            @override
            public void onanimationrepeat(animator animation) {}
        });
        animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
            @override
            public void onanimationupdate(valueanimator animation) {
                int current = (int) animator.getanimatedvalue();
                log("current=" + current);

                layoutparams.x = math.abs(current);
                mwindowmanager.updateviewlayout(view, layoutparams);
            }
        });
        animator.start();
    }
}

同样是通过改变button的x和y值来达到滑动效果,只不过我只需要x平移,y为0,需要斜着滑的你们可*发挥,为了使滑动看上去平滑,给动画添加了一个线性插值器,设置滑动时间,监听返回插值进度,这样动态设置给button。为了保存button的最终位置,添加了一个动画完成监听,并将x和y写入到sharedpreferences中保存。

四、添加点击返回功能

通过打印日志分析,系统导航栏的返回按键,发现其原理是通过keybuttonview的触摸事件发送一个keyevent事件给系统来实现返回功能

源码位置frameworks\base\packages\systemui\src\com\android\systemui\statusbar\policy\keybuttonview.java

public boolean ontouchevent(motionevent ev) {
    final int action = ev.getaction();
    int x, y;
    if (action == motionevent.action_down) {
        mgestureaborted = false;
    }
    if (mgestureaborted) {
        return false;
    }

    switch (action) {
        case motionevent.action_down:
            //按下的时间
            mdowntime = systemclock.uptimemillis();
            setpressed(true);
            if (mcode != 0) {//按下事件
                sendevent(keyevent.action_down, 0, mdowntime);
            } else {
                // provide the same haptic feedback that the system offers for virtual keys.
                performhapticfeedback(hapticfeedbackconstants.virtual_key);
            }
            removecallbacks(mchecklongpress);
            postdelayed(mchecklongpress, viewconfiguration.getlongpresstimeout());
            break;
        case motionevent.action_move:
            x = (int)ev.getx();
            y = (int)ev.gety();
            setpressed(x >= -mtouchslop
                    && x < getwidth() + mtouchslop
                    && y >= -mtouchslop
                    && y < getheight() + mtouchslop);
            break;
        case motionevent.action_cancel:
            setpressed(false);
            if (mcode != 0) {
                sendevent(keyevent.action_up, keyevent.flag_canceled);
            }
            removecallbacks(mchecklongpress);
            break;
        case motionevent.action_up:
            final boolean doit = ispressed();
            setpressed(false);
            if (mcode != 0) {
                if (doit) {//抬起事件
                    sendevent(keyevent.action_up, 0);
                    sendaccessibilityevent(accessibilityevent.type_view_clicked);
                    playsoundeffect(soundeffectconstants.click);
                } else {
                    sendevent(keyevent.action_up, keyevent.flag_canceled);
                }
            } else {
                // no key code, just a regular imageview
                if (doit) {
                    performclick();
                }
            }
            removecallbacks(mchecklongpress);
            break;
    }

    return true;
}

//以下为我们给button添加的点击事件
private void sendevent(int action, int flags, long when) {
    int mcode = 4;
    log.e(tag, "mcode="+mcode + "  flags="+flags);
    final int repeatcount = (flags & keyevent.flag_long_press) != 0 ? 1 : 0;
    final keyevent ev = new keyevent(when - 100, when, action, mcode, repeatcount,
            0, keycharactermap.virtual_keyboard, 0,
            flags | keyevent.flag_from_system | keyevent.flag_virtual_hard_key,
            inputdevice.source_keyboard);
    inputmanager.getinstance().injectinputevent(ev,
            inputmanager.inject_input_event_mode_async);
}

button.setonclicklistener(new view.onclicklistener() {
        @override
        public void onclick(view v) {
            log.e(tag,"click dragbutton ...");
            final long mdowntime = systemclock.uptimemillis();
            //onbackpressed();
            sendevent(keyevent.action_down, 0, mdowntime);

            new handler().postdelayed(new runnable() {
                @override
                public void run() {
                    sendevent(keyevent.action_up, 0, systemclock.uptimemillis());
                }
            }, 300);
        }
    });

需要注意的地方,系统返回键对应的code为4,所以mcode=4,keybuttonview的触摸事件包含按下和抬起,所以我们只需模拟发送按下和抬起事件,可以看到抬起事件加了300ms的延时发送,这是关键不然系统不会处理。