背景

想必用过Android腾讯手机管家或者做过相关研发的童鞋都会发现腾讯做了一个类似于iOS系统下的Panel功能,如下图一。

即从屏幕的底部或者侧部用手势划出快捷中心控制面板,如下图二。

有别于以前的快捷控制方案,以前的快捷控制方案是在下拉的通知栏里去做控制,如下图三的360方案,这个功能算是腾讯的一个微创新,要点赞。

设计原理

看起来很高大上有木有。其实如果掌握了原理是非常简单的,就是运用了Android里的WindowManager技术,这个在很多国内各色各样的手机管家技术里用到的悬浮窗、一键加速是一样一样的(这里就不扩展了,网上一搜一大把),我猜腾讯手机管家用了一个小小的魔术,是什么呢,在图二中看到的选择后出现的蓝色小方块,在退出该界面后是没有的,但是实际上仍然预留了这样一个区域,只是色值设为透明了,通过监听用户在该区域上的手势滑动,从而拖出快捷中心面板。

实现方案

  1. 实现FloatWindowQuickCenterTouch,用于实现监听手势拖出快捷中心
  2. 实现FloatWindowQuickCenterShow,用于实现设置界面选择后的蓝色展示区域
  3. 实现FloatWindowQuickCenter,用于实现快捷中心具体的界面和逻辑
  4. 写一个FloatWindowManager管理类,实现以上几个悬浮窗的实例化和WindowManager实例与addView
  5. 在一个Service中运用TimeTask监听悬浮窗的加载状态

代码方案

  • FloatWindowQuickCenterTouch关键代码
FloatWindowQuickCenterTouch主要是用于建立一块在桌面端的透明区域,通过监听手势拖出我们需要的快捷中心面板

/*** @Function Touch区域实例化*/
public FloatWindowQuickCenterTouch(Context context, int position) {// TODO Auto-generated constructor stubsuper(context);this.mContext = context;this.mPosition = position; //因为涉及到显示位置if (position != QuickCenterParams.POSITION_RIGHT && position != QuickCenterParams.POSITION_BOTTOM) position = QuickCenterParams.POSITION_RIGHT;View touchView = new View(context); LinearLayout.LayoutParams touchParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);if (position == QuickCenterParams.POSITION_RIGHT) { //支持右边和底部touchParams.height = mContext.getResources().getInteger(R.dimen.quick_view_right_height);touchParams.width = mContext.getResources().getInteger(R.dimen.quick_view_right_width);      } else if (position == QuickCenterParams.POSITION_BOTTOM) {touchParams.height = mContext.getResources().getInteger(R.dimen.quick_view_bottom_height);touchParams.width = GlobalApp.screenWidth; //整个屏幕的宽度}  touchView.setLayoutParams(touchParams);touchView.setBackgroundColor(getResources().getColor(R.color.transparent)); //注意我用了透明色this.addView(touchView);
}

上面是这个View的实例化,之后再manager类中就是通过调用这个方法来create这个View;

之后便是手势的监听,根据手势在该View上的位移来操作快捷中心面板的拖出操作;
/*** @Function 重写touchevent* 监听手势的位置位移,来创建快捷中心面板* 注意要用getRawX/Y方法,与getX/Y的区别请自行百度*/
@Override
public boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch (event.getAction()) {case MotionEvent.ACTION_DOWN:xInView = event.getX();yInView = event.getY();xInScreen = event.getRawX();yInScreen = event.getRawY() - getStatusBarHeight(); //减去状态栏的高度break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP:xOutScreen = event.getRawX();yOutScreen = event.getRawY();if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) //横屏不执行拖出面板return super.onTouchEvent(event);if (mPosition == QuickCenterParams.POSITION_RIGHT) {if (xInScreen - xOutScreen > GlobalApp.screenWidth / 5) {FloatWindowManager.createQuickCenterWindow(mContext, mPosition); //这个就是之前提到的Manager类创建快捷中心的方法,之后在介绍}} else if (mPosition == QuickCenterParams.POSITION_BOTTOM) {if (yInScreen - yOutScreen > GlobalApp.screenHeight / 6) {FloatWindowManager.createQuickCenterWindow(mContext, mPosition);}}break;default:break;}return super.onTouchEvent(event);
}

附送上一个获取状态栏高度的方法

/*** 用于获取状态栏的高度。* @return 返回状态栏高度的像素值。*/
public int getStatusBarHeight() {if (mStatusBarHeight == 0) {try {Class<?> c = Class.forName("com.android.internal.R$dimen");Object o = c.newInstance();Field field = c.getField("status_bar_height");int x = (Integer) field.get(o);mStatusBarHeight = getResources().getDimensionPixelSize(x);} catch (Exception e) {e.printStackTrace();}}return mStatusBarHeight;
}
  • FloatWindowQuickCenterShow关键代码
FloatWindowQuickCenterShow主要通过建立一块有色的和Touch相同大小的区域,在设置界面展示给用户
/*** @Function Show区域实例化* 与Touch类是一样的,只是改变了颜色,并且不用实现onTouchEvent监听了*/
public FloatWindowQuickCenterShow(Context context, int position) {super(context);if (position != QuickCenterParams.POSITION_RIGHT && position != QuickCenterParams.POSITION_BOTTOM) position = QuickCenterParams.POSITION_RIGHT;View showView = new View(context);  LinearLayout.LayoutParams showParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);if (position == QuickCenterParams.POSITION_RIGHT) {showParams.height = context.getResources().getInteger(R.dimen.quick_view_right_height);showParams.width = context.getResources().getInteger(R.dimen.quick_view_right_width);     } else if (position == QuickCenterParams.POSITION_BOTTOM) {showParams.height = context.getResources().getInteger(R.dimen.quick_view_bottom_height);showParams.width = GlobalApp.screenWidth;}   showView.setLayoutParams(showParams);showView.setBackgroundColor(getResources().getColor(R.color.blue)); //其他都一样,就是这里换成了蓝色this.addView(showView);
}
  • FloatWindowQuickCenter关键代码
这个是面板拖出来后的主要逻辑,里面的内容点还挺多也很杂,杂主要是因为Android碎片化严重,调用系统的功能,你能兼顾各个版本吧,兼顾各个厂商吧,之后我再写一篇来讲里面可能涉及到的问题点。就先贴一下创建的代码好了。
/*** @Function 快捷中心面板* 继承至LinearLayout,上面的几个view都是继承于它,便于简洁只写了这个,在此一并说明*/
public class FloatWindowQuickCenter extends LinearLayout {/*** @Function 快捷中心实例化* 这里我没有动态加载view,而是用了xml布局*/public FloatWindowQuickCenter(Context context, int position) {super(context);mView = LayoutInflater.from(context).inflate(R.layout.view_floatwindow_quickcenter, this);this.mPosition = position;this.mContext = context;//其他功能代码}//其他功能代码
}
  • FloatWindowManager关键代码
这个是自己建立的悬浮窗管理类,里面包含了创建与删除所有悬浮窗的方法,现在为了简洁只取其中一个来展示,其他都可以照猫画虎。里面的方法都采用static,方便直接调用,工具类一般如此写。

/*** @Function 悬浮窗管理类* @author rivers*/
public class FloatWindowManager {/*** 用于控制在屏幕上添加或移除悬浮窗*/private static WindowManager mWindowManager;/*** 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。* * @param context*            必须为应用程序的Context.* @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。*/private static WindowManager getWindowManager(Context context) {if (mWindowManager == null) {mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);}return mWindowManager;}/*** 快捷中心实例*/private static FloatWindowQuickCenter mFloatWindowQuickCenter;/*** 快捷中心参数*/private static LayoutParams mQuickCenterParams;/*** 快捷中心create方法*/public static void createQuickCenterWindow(Context context, int position) {mWindowManager = getWindowManager(context); //检测windowmanager是否已经创建if (mFloatWindowQuickCenter == null) {mFloatWindowQuickCenter = new FloatWindowQuickCenter(context, position);mQuickCenterParams = new LayoutParams();mQuickCenterParams.type = LayoutParams.TYPE_SYSTEM_ALERT;mQuickCenterParams.format = PixelFormat.RGBA_8888;mQuickCenterParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL;mQuickCenterParams.width = android.view.ViewGroup.LayoutParams.MATCH_PARENT;mQuickCenterParams.height = android.view.ViewGroup.LayoutParams.MATCH_PARENT;mQuickCenterParams.x = 0;mQuickCenterParams.y = 0;if (position == QuickCenterParams.POSITION_RIGHT) {mQuickCenterParams.windowAnimations = android.R.style.Animation_Translucent;} else if (position == QuickCenterParams.POSITION_BOTTOM) {mQuickCenterParams.windowAnimations = android.R.style.Animation_InputMethod;}mWindowManager.addView(mFloatWindowQuickCenter, mQuickCenterParams);}}/*** 快捷中心remove方法*/public static void removeQuickCenterWindow(Context context) {if (mFloatWindowQuickCenter != null) {mFloatWindowQuickCenter.clearAnimation();mFloatWindowQuickCenter.unregisterReceiver();mWindowManager.removeView(mFloatWindowQuickCenter);mFloatWindowQuickCenter = null;}}//其他的功能代码//如show、touch都用类似的方法去执行创建和删除
}

需要注意的是LayoutParams这个参数,不是LinearLayout或者RelativeLayout下的LayoutParams,包全称为android.view.WindowManager.LayoutParams,是WindowManager下的专属参数,在create方法中也看到一些参数的定义了,是很重要的,直接影响到利用WindowManager创建的这个悬浮窗的级别和呈现方法、可操作性等。下面简单的介绍一下我用到的参数,更多的可以Google。

首先介绍LayoutParams.type
大概就是设置你的悬浮窗的类型,实际上是一个级别,我用的是TYPE_SYSTEM_ALERT = 2003,查阅API介绍为 Window type: system window,系统级的窗口,这样我们的所需要的窗口就能显示在界面了。
/*** Start of system-specific window types.  These are not normally* created by applications.*/
public static final int FIRST_SYSTEM_WINDOW     = 2000;/*** Window type: the status bar.  There can be only one status bar* window; it is placed at the top of the screen, and all other* windows are shifted down so they are below it.* In multiuser systems shows on all users' windows.*/
public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;/*** Window type: the search bar.  There can be only one search bar* window; it is placed at the top of the screen.* In multiuser systems shows on all users' windows.*/
public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;/*** Window type: phone.  These are non-application windows providing* user interaction with the phone (in particular incoming calls).* These windows are normally placed above all applications, but behind* the status bar.* In multiuser systems shows on all users' windows.*/
public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;/*** Window type: system window, such as low power alert. These windows* are always on top of application windows.* In multiuser systems shows only on the owning user's window.*/
public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;

接下来介绍LayoutParams.flag

常用的有FLAG_NOT_FOCUSABLE,FLAG_NOT_TOUCHABLE,FLAG_NOT_TOUCH_MODAL,按照我理解的意思分别为:
1.如果使用了就touch事件就会被拦截,悬浮窗下面的view事件就不能触发了,如我在设置界面的展示区域就设置了这个flag,不然就不能操作了
2.touch事件不能用,永远接受不到touch event
3.即使这个窗口被聚焦了,允许事件传递到下一层,在我们定义的窗口之外,所以一般会用这个,如dialog
以上为我的理解,不对的地方请指正,下面贴一下官方的注释
/*** Window type: system window, such as low power alert. These windows* are always on top of application windows.* In multiuser systems shows only on the owning user's window.*/
public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;/** Window flag: this window won't ever get key input focus, so the* user can not send key or other button events to it.  Those will* instead go to whatever focusable window is behind it.  This flag* will also enable {@link #FLAG_NOT_TOUCH_MODAL} whether or not that* is explicitly set.* * <p>Setting this flag also implies that the window will not need to* interact with* a soft input method, so it will be Z-ordered and positioned * independently of any active input method (typically this means it* gets Z-ordered on top of the input method, so it can use the full* screen for its content and cover the input method if needed.  You* can use {@link #FLAG_ALT_FOCUSABLE_IM} to modify this behavior. */
public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;/** Window flag: this window can never receive touch events. */
public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;/** Window flag: even when this window is focusable (its* {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events* outside of the window to be sent to the windows behind it.  Otherwise* it will consume all pointer events itself, regardless of whether they* are inside of the window. */
public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;

再接下来是LayoutParams.windowAnimations,主要是窗口弹出的动画效果,我在项目中根据我的需求,右边我就用android.R.style.Animation_Translucent风格,位移推出快捷中心窗口,如果设置的从下拉出,则用android.R.style.Animation_InputMethod风格,类似于键盘的弹出,还有其他的风格,比如toast,dialog等,TX们可以自行google或者参看。

至于其他的宽高,xy坐标我就不一一介绍了。

  • Service关键代码
需用Service来创建和监听快捷中心,由于是桌面级的,我们只能用service在后台去控制manager类,并且用一个TimerTask去检测Touch区域的开启和关闭状态。
public class FloatWindowService extends Service {@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {// 开启定时器mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);if (timer == null) {timer = new Timer();RefreshTask freshTask=new RefreshTask();timer.scheduleAtFixedRate(freshTask, 0, 1400);}return super.onStartCommand(intent, flags, startId);}class RefreshTask extends TimerTask {@Overridepublic void run() {String pckName = getRunningPck();if (pckName != null && !TextUtils.isEmpty(pckName)) {if (currentPkgName == null) {currentPkgName = pckName;doRefresh();} else if (!currentPkgName.equalsIgnoreCase(pckName)) {currentPkgName = pckName;doRefresh();}}}}public void doRefresh() {doRefreshFloat();//其他功能代码}public void doRefreshFloat() {if (SharedUtil.getQuickCenterStatus()) { //检测开启状态handler.post(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubFloatWindowManager.createQuickCenterTouchArea(getApplicationContext(), SharedUtil.getQuickCenterPosition());}});} else {FloatWindowManager.removeQuickCenterTouchArea(getApplication());FloatWindowManager.removeQuickCenterWindow(getApplicationContext());}}//其他功能代码
}

总结

这个一整套下来,一个类似于腾讯手机关键快捷中心的快捷方式就已经算是完成了,由于个人原因大概只能写了一个思路,尽可能的贴出关键代码,如果有需要的TX可以留言交流。
调出的原理懂了,至于快捷中心里面的东西就看需求个人发挥了,无非就是写一个布局,点击后实现功能罢了。这个我如果后面有时间会再写一片编码快捷中心面板时遇到的问题 ,由于是调用系统的功能,所以主要是碎片化的问题,包括版本适配(我从2.3适配到了5.0),以及厂商的适配等。

遗憾

腾讯还是很牛的,有一个效果我一直没有实现,即我是通过在Touch区域监听手势滑动然后用动画效果拖出了快捷中心面板。而腾讯手机管家,可以真正做到抽屉(Panel)的效果,即可以在做手势的同时,没有UP的情况下,快捷中心面板就跟着手势滑动出来了,佩服佩服。

为什么佩服,做过Panel的童鞋会懂,如果你要拖出一个区域,那必须这个区域已经存在了,这样势必会覆盖住下面的东西,虽然没拖出来之前是显示空白,但是下面还是没法点击的。我百思不得其解,也研究了几天,目前仍未有答案,有相关经验的TX请告之,小弟感激不尽。

仿腾讯手机管家快捷中心功能的实现方案相关推荐

  1. 【Android进阶】如何写一个很屌的动画(3)---高仿腾讯手机管家火箭动画

    系列中其他文章: [Android进阶]如何写一个很屌的动画(1)-先实现一个简易的自定义动画框架 [Android进阶]如何写一个很屌的动画(2)-动画的好帮手们 [Android进阶]如何写一个很 ...

  2. 仿腾讯手机管家火箭发射

    好久没有写过博客了,前段时间一个项目中用到了浮点(漂浮在窗体上),于是突发灵感发现可以实现类似于腾讯手机管家火箭升空效果  实现步骤:1:新建一个类 名为RocketView(用来显示浮点,当手指拖动 ...

  3. 仿腾讯手机管家火箭发射案例

    概述 和腾讯管家类似,360手机卫士上也有类似的功能:拖动小球到屏幕底部,然后小球变成火箭,松手后火箭发射.虽然两者的UI效果各有千秋,但原理基本上是相同的.因为时间的关系,我只实现了部分的UI效果, ...

  4. Android仿腾讯手机管家实现桌面悬浮窗小火箭发射的动画效果

    功能分析:  1.小火箭游离在activity之外,不依附于任何activity,不管activity是否开启,不影响小火箭的代码逻辑,所以小火箭的代码逻辑是要写在服务中:  2.小火箭挂载在手机窗体 ...

  5. 安全管家安卓_手机丢失后可能背负巨额债务,腾讯手机管家提醒注意手机安全防护 -...

    之前骗子偷手机是为了卖手机换钱,现在骗子偷手机,是为了用你的信息各平台套现.近期,一篇名为<一部手机失窃而揭露的窃取个人信息实现资金盗取的黑色产业链>引发广泛关注,工信部约谈相关单位,并提 ...

  6. Android用户分类管理,腾讯手机管家Android 7.10上线,微信整理助手实现智能精准分类...

    如今,大家习惯打开微信,或查看好友朋友圈动态,或浏览热点新闻.微信作为高频应用,带来了更加智能.便捷的生活,同时也让大家开始思考:当微信接收的信息越来越多,如何实现高效管理? 近日,腾讯手机管家上线了 ...

  7. android手机管家文档,腾讯手机管家Android 7.10上线,微信整理助手实现智能精准分类...

    如今,大家习惯打开微信,或查看好友朋友圈动态,或浏览热点新闻.微信作为高频应用,带来了更加智能.便捷的生活,同时也让大家开始思考:当微信接收的信息越来越多,如何实现高效管理? 近日,腾讯手机管家上线了 ...

  8. 计算机在线给手机杀毒,国家计算机病毒应急处理中心曝光6款恶意应用 腾讯手机管家实现精准查杀...

    河北的小罗为了研究星座运势下载了一款星座解读类软件,结果手机不停死机只好送去维修.病毒类APP一直困扰着用户手机的正常使用,近日,国家计算机病毒应急处理中心曝光了6款恶意应用.这些恶意应用存在推送广告 ...

  9. 高仿QQ的手机管家的小火箭加速

    高仿QQ的手机管家的小火箭加速 1.前言 腾讯的手机管家,用过这个App的人都应该知道桌面的火箭一键加速这个功能,研究一下这个小火箭是怎么做出来的.先来了解一下小火箭有神马动作,首先在没有触碰它时,就 ...

最新文章

  1. mysql ef6 事务_使用事务-EF6 | Microsoft Docs
  2. vmware虚拟机redhat7.2下docker容器安装hadoop
  3. 【微信小程序企业级开发教程】前台收集数据更新数据库表方法
  4. 2010.9.29 今日问题
  5. 苹果挂端口方法_调音台变身直播声卡的方法
  6. wpf 代码获取contextmenu_[C#] 转:在WPF里面获取右键弹出菜单(ContextMenu)的鼠标点击源(Owner)控件...
  7. 背地砖上楼的机器人_德国发明铺地砖机器人,效率大幅度上升,节省千万!
  8. java内置排序有哪些_内部排序比较(Java版)
  9. 28.TCP/IP 详解卷1 --- SMTP:简单邮件传输协议
  10. [C#] LINQ之GroupBy
  11. 线性代数笔记5——平面方程与矩阵
  12. html 做报表,创建 HTML 报表
  13. PSRAM/SRAM与XMC硬件连接的推荐方法
  14. 7-214 泰勒级数展开近似sin(x)的值7-215 求班级平均分7-216 同数异形体
  15. 学习笔记——OFDM仿真课设
  16. java开发的微信公众号服务端生产环境中的两个大坑
  17. 【一周头条盘点】中国软件网(2018.5.7~2018.5.11)
  18. 转:MIME(Multipurpose Internet Mail Extensions)类型
  19. 谷歌翻译接口使用(android为例)
  20. instr()函数的格式

热门文章

  1. 搜狗网址导航 php,sogou 搜狗网址导航的源码,将整站仿了下来,完美无错,纯净,精仿 面绝对 全免费 WEB(ASP,PHP,...) 238万源代码下载- www.pudn.com...
  2. python怎么画极坐标,python极坐标的绘制
  3. 随机预言机(random oracle)和PRF(Pseudorandom Function)是什么,区别在哪里?
  4. 电商网站如何推广自己呢?
  5. HDU 1427 速算24点(DFS) *
  6. 2022-2028年中国第三方电子支付市场投资分析及前景预测报告
  7. 三大攻略破TD手机节能之困
  8. Python学习:choice()与choices()、sample()的区别
  9. Vue使用Antv F2
  10. linux sudo yum命令详解,Linux apt-get