前言

之前写过屏蔽系统导航栏功能的文章,具体可看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. 一些常用的linux命令(2)

    参考:http://www.cnblogs.com/laov/p/3541414.html 系统管理命令 stat               显示指定文件的详细信息,比ls更详细 who       ...

  2. http://www.runoob.com/lua/lua-basic-syntax.html

    Lua优点及特性 Lua 是一个小巧的脚本语言. 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能.Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行.Lua并没 ...

  3. EXCEL解析之终极方法WorkbookFactory

    Selenium做自动化测试当然不能避免和Excel打交道. 由于Excel版本的关系,文件扩展名分xls和xlsx, 以往的经验都是使用HSSFWorkbook和XSSFWorkbook来分别处理. ...

  4. SSM-SpringMVC-07:SpringMVC中处理器映射器

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- BeanNameUrlHandlerMapping和SimpleUrlHandlerMapping BeanN ...

  5. 推荐《用Python进行自然语言处理》中文翻译-NLTK配套书

    NLTK配套书<用Python进行自然语言处理>(Natural Language Processing with Python)已经出版好几年了,但是国内一直没有翻译的中文版,虽然读英文 ...

  6. java 泛型详解(普通泛型、 通配符、 泛型接口,泛型数组,泛型方法,泛型嵌套)

    JDK1.5 令我们期待很久,可是当他发布的时候却更换版本号为5.0.这说明Java已经有大幅度的变化.本文将讲解JDK5.0支持的新功能-----Java的泛型.  1.Java泛型  其实Java ...

  7. SwipeListView 详解 实现微信,QQ等滑动删除效果

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/28508769 今天看别人项目,看到别人使用了SwipeListView,Goog ...

  8. yii2.0 路由美化以及自定义设置

    在配置文件里修改: 如果把'enablePrettyUrl' => true,设为true那么你的gii就访问不到: 'rules' => [                 '<c ...

  9. Python 下载win32api 模块

    终端输入 pip install pypiwin32

  10. config.go

    package blog4go import ( "encoding/xml" "errors" "io/ioutil" "os& ...