Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮
前言
之前写过屏蔽系统导航栏功能的文章,具体可看android6.0 源码修改之屏蔽导航栏虚拟按键(home和recentapp)/动态显示和隐藏navigationbar
在某些特殊定制的版本中要求完全去掉导航栏,那么当用户点进一些系统自带的应用界面如设置、联系人等,就没法退出了,虽然可以在actionbar中添加back按钮,但总不能每一个app都去添加吧。所以灵机一动我们就给系统添加一个全屏可拖拽的浮窗按钮,点击的时候处理返回键的逻辑。它大概长这样(审美可能丑了点,你们可以*发挥)
图1 最终效果图
思路分析
- 通过分析之前的navigationbar代码,发现系统是通过windowmanager添加view的方式来实现,此处我们也可以模拟这种方法来添加
- 添加悬浮窗以后监听触摸事件,跟随手指移动重新修改view的layoutparam
- 松手后获取当前x坐标,小于屏幕width的一半则平移归位至屏幕左边
- 添加系统的返回按键功能
一、添加悬浮窗
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的延时发送,这是关键不然系统不会处理。