前言

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

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



图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的延时发送,这是关键不然系统不会处理。

Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮的更多相关文章

  1. Android6.0 源码修改之 Contacts应用

    一.Contacts应用的主界面和联系人详情界面增加顶部菜单添加退出按钮 通过Hierarchy View 工具可以发现 主界面对应的类为 PeopleActivity 联系人详情界面对应的类为 Qu ...

  2. Android6.0 源码修改之屏蔽系统短信功能和来电功能

    一.屏蔽系统短信功能 1.屏蔽所有短信 android 4.2 短信发送流程分析可参考这篇 戳这 源码位置 vendor\mediatek\proprietary\packages\apps\Mms\ ...

  3. Android6.0 源码修改之Settings音量调节界面增加通话音量调节

    前言 今天客户提了个需求,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就来了.需要优化两个地方 1.在正常情 ...

  4. Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar

    场景分析, 为了完全实现沉浸式效果,在进入特定的app后可以将导航栏移除,当退出app后再次将导航栏恢复.(下面将采用发送广播的方式来移除和恢复导航栏) ps:不修改源码的情况下,简单的沉浸式效果实现 ...

  5. Android6.0 源码修改之Setting列表配置项动态添加和静态添加

    写在前面 最近客户有个需求,要求增加操作Setting列表配置项的功能,是不是一脸懵,没关系,一图胜千言,接下来就上图.诺,就是这么个意思.   原来的列表配置项     增加了单个配置项     增 ...

  6. 在Ubuntu Server14.04上编译Android6.0源码

    此前编译过Android4.4的源码,但是现在Android都到了7.0的版本,不禁让我感叹Google的步伐真心难跟上,趁这周周末时间比较充裕,于是在过去的24小时里,毅然花了9个小时编译了一把An ...

  7. Android6.0源码下载编译刷入真机

    编译环境是Ubuntu12.04.手机nexus 5,编译安卓6.0.1源码并烧录到真机. 源码用的是科大的镜像:http://mirrors.ustc.edu.cn/aosp-monthly/,下载 ...

  8. Ubuntu16.04下编译android6.0源码

    http://blog.csdn.net/cnliwy/article/details/52189349 作为一名合格的android开发人员,怎么能不会编译android源码呢!一定要来一次说编译就 ...

  9. Android6.0源码分析之录音功能(一)【转】

    本文转载自:http://blog.csdn.net/zrf1335348191/article/details/54949549 从现在开始一周时间研究录音,下周出来一个完整的博客,监督,激励!!! ...

随机推荐

  1. 什么是web service ?

    一.序言 大家或多或少都听过WebService(Web服务),有一段时间很多计算机期刊.书籍和网站都大肆的提及和宣传WebService技术,其中不乏很多吹嘘和做广告的成分.但是不得不承认的是Web ...

  2. ES的基本用法

    ES的基本概念 1> 集群和节点 一个es集群是由一个或多和es节点组成的集合 每一个集群都有一个名字, 如之前的wali 每个节点都有自己的名字, 如之前的master, slave1, sl ...

  3. linux下c编程 基础

    1. 熟悉Linux系统下的开发环境 2. 熟悉vi的基本操作 3. 熟悉gcc编译器的基本原理 4. 熟练使用gcc编译器的常用选项 5 .熟练使用gdb调试技术 6. 熟悉makefile基本原理 ...

  4. grub rescue 主引导修复

    使用windows 和 ubuntu 双系统的人,很有可能碰到重装某一个系统,或者另外添加分区,导致系统重启出现 : GRUB loading error:unknow filesystem grub ...

  5. CMD命令锦集

    虽然随着计算机产业的发展,Windows 操作系统的应用越来越广泛,DOS 面临着被淘汰的命运,但是因为它运行安全.稳定,有的用户还在使用,所以一般Windows 的各种版本都与其兼容,用户可以在Wi ...

  6. 使用Spring MVC测试Spring Security Oauth2 API

    不是因为看到希望了才去坚持,而坚持了才知道没有希望. 前言 在Spring Security源码分析十一:Spring Security OAuth2整合JWT和Spring Boot 2.0 整合 ...

  7. centos7服务器无GUI情况下安装使用Xvfb、selenium、chrome和selenium-server

    最近需要用到selenium浏览器抓取,在windows下对照chrome浏览器开发的代码,在linux服务器上换成phantomjs驱动后,却不能运行了,通过截图发现phantomjs渲染效果和ch ...

  8. React-router v4教程

    在这个教程里,我们会从一个例子React应用开始学习react-router-dom.其中你会学习如何使用Link.NavLink等来实现跳转,Switch和exact实现排他路由和浏览器路径历史. ...

  9. json & pickle 序列化

    #!/usr/bin/python # -*- coding: utf-8 -*- # 序列化: 例如把字典写进文件 info = { 'name': 'alex', 'age': 22 } f = ...

  10. Django中模板过滤器总结

    一.形式:小写: {{ name | lower }} 二.串联:先转义文本到HTML,再转换每行到 <p> 标签: {{ my_text|escape|linebreaks } 三.过滤 ...