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的延时发送,这是关键不然系统不会处理。
Android6.0 源码修改之 仿IOS添加全屏可拖拽浮窗返回按钮的更多相关文章
- Android6.0 源码修改之 Contacts应用
一.Contacts应用的主界面和联系人详情界面增加顶部菜单添加退出按钮 通过Hierarchy View 工具可以发现 主界面对应的类为 PeopleActivity 联系人详情界面对应的类为 Qu ...
- Android6.0 源码修改之屏蔽系统短信功能和来电功能
一.屏蔽系统短信功能 1.屏蔽所有短信 android 4.2 短信发送流程分析可参考这篇 戳这 源码位置 vendor\mediatek\proprietary\packages\apps\Mms\ ...
- Android6.0 源码修改之Settings音量调节界面增加通话音量调节
前言 今天客户提了个需求,因为我们的设备在正常情况下无法调节通话音量,只有在打电话过程中,按物理音量加减键才能出现调节通话音量seekBar,很不方便,于是乎需求就来了.需要优化两个地方 1.在正常情 ...
- Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar
场景分析, 为了完全实现沉浸式效果,在进入特定的app后可以将导航栏移除,当退出app后再次将导航栏恢复.(下面将采用发送广播的方式来移除和恢复导航栏) ps:不修改源码的情况下,简单的沉浸式效果实现 ...
- Android6.0 源码修改之Setting列表配置项动态添加和静态添加
写在前面 最近客户有个需求,要求增加操作Setting列表配置项的功能,是不是一脸懵,没关系,一图胜千言,接下来就上图.诺,就是这么个意思. 原来的列表配置项 增加了单个配置项 增 ...
- 在Ubuntu Server14.04上编译Android6.0源码
此前编译过Android4.4的源码,但是现在Android都到了7.0的版本,不禁让我感叹Google的步伐真心难跟上,趁这周周末时间比较充裕,于是在过去的24小时里,毅然花了9个小时编译了一把An ...
- Android6.0源码下载编译刷入真机
编译环境是Ubuntu12.04.手机nexus 5,编译安卓6.0.1源码并烧录到真机. 源码用的是科大的镜像:http://mirrors.ustc.edu.cn/aosp-monthly/,下载 ...
- Ubuntu16.04下编译android6.0源码
http://blog.csdn.net/cnliwy/article/details/52189349 作为一名合格的android开发人员,怎么能不会编译android源码呢!一定要来一次说编译就 ...
- Android6.0源码分析之录音功能(一)【转】
本文转载自:http://blog.csdn.net/zrf1335348191/article/details/54949549 从现在开始一周时间研究录音,下周出来一个完整的博客,监督,激励!!! ...
随机推荐
- Fiddler证书安装(查看HTTPS)
现在很多带有比较重要信息的接口都使用了安全性更高的HTTPS,而Fiddler默认是抓取HTTP类型的接口,要想查看HTTPS类型接口就需要安装fiddler证书. fiddler安装教程可参考: ...
- Python_socket_UDP
zReceiver.py import socket #使用ipv4协议,使用UDP协议传输数据 s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # ...
- react 高阶组件的 理解和应用
高阶组件是什么东西 简单的理解是:一个包装了另一个基础组件的组件.(相对高阶组件来说,我习惯把被包装的组件称为基础组件) 注意:这里说的是包装,可以理解成包裹和组装: 具体的是高阶组件的两种形式吧: ...
- Spring Data JPA 初体验
一,JPA相关的概念 JPA概述 全称是:JavaPersistence API.是SUN公司推出的一套基于ORM的规范. Hibernate框架中提供了JPA的实现. JPA通过JDK 5.0注解或 ...
- Spring Boot全局支持CORS(跨源请求)的配置方法
http://blog.csdn.net/zhangchao19890805/article/details/53893735
- Thread部分总结以及小例子
Thread总结:一直以来用thread比较多,一般会在同步以及ui中用到.以下对于经常用作为简单介绍.一 实现方法: 一种直接new thread,另外一种是实现Runnable接口,在创建thre ...
- knockout + easyui = koeasyui
在做后台管理系统的同学们,是否有用easyui的经历.虽然现在都是vue.ng.react的时代.但easyui(也就是jquery为基础)还是占有一席之地的.因为他对后端开发者太友好了,太熟悉不过了 ...
- 使用java检测网络连接状况
windows中可以通过在cmd中使用ping命令来检测网络连接状况,如下: 网络连接正常时: 网络未连接时: 在java中可以通过调用ping命令来判断网络是否连接正常: package modul ...
- 在MFC中使用按下按钮出现选择文件对话框,选中一个指定文件,并将其地址显示到指定的编辑框中
其中,我们选择的文件后缀名为.xlsx,以只读和写操作方式,在所有的文件中选择.xlsl文档 CFileDialog dlg(true, _T(".xlsx"), NULL, OF ...
- 【BZOJ 2673】[Wf2011]Chips Challenge
题目大意: 传送门 $n*n$的棋盘,有一些位置可以放棋子,有一些已经放了棋子,有一些什么都没有,也不能放,要求放置以后满足:第i行和第i列的棋子数相同,同时每行的棋子数占总数比例小于$\frac{A ...