现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作。今天这篇文章,就是介绍如何实现桌面悬浮窗效果的。

首先,看一下效果图。

悬浮窗一共分为两个部分,一个是平常显示的小窗口,另外一个是点击小窗口显示出来的二级悬浮窗口。

首先,先看一下这个项目的目录结构。

最关键的就是红框内的四个类。

首先,FloatWindowService是一个后台的服务类,主要负责在后台不断的刷新桌面上的小悬浮窗口,否则会导致更换界面之后,悬浮窗口也会随之消失,因此需要不断的刷新。下面是实现代码。

[java] view plain copy  
  1. package com.qust.floatwindow;
  2. import java.util.Timer;
  3. import java.util.TimerTask;
  4. import android.app.Service;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.os.Handler;
  8. import android.os.IBinder;
  9. /**
  10. * 悬浮窗后台服务
  11. *
  12. * @author zhaokaiqiang
  13. *
  14. */
  15. public class FloatWindowService extends Service {
  16. public static final String LAYOUT_RES_ID = "layoutResId";
  17. public static final String ROOT_LAYOUT_ID = "rootLayoutId";
  18. // 用于在线程中创建/移除/更新悬浮窗
  19. private Handler handler = new Handler();
  20. private Context context;
  21. private Timer timer;
  22. // 小窗口布局资源id
  23. private int layoutResId;
  24. // 布局根布局id
  25. private int rootLayoutId;
  26. @Override
  27. public int onStartCommand(Intent intent, int flags, int startId) {
  28. context = this;
  29. layoutResId = intent.getIntExtra(LAYOUT_RES_ID, 0);
  30. rootLayoutId = intent.getIntExtra(ROOT_LAYOUT_ID, 0);
  31. if (layoutResId == 0 || rootLayoutId == 0) {
  32. throw new IllegalArgumentException(
  33. "layoutResId or rootLayoutId is illegal");
  34. }
  35. if (timer == null) {
  36. timer = new Timer();
  37. // 每500毫秒就执行一次刷新任务
  38. timer.scheduleAtFixedRate(new RefreshTask(), 0, 500);
  39. }
  40. return super.onStartCommand(intent, flags, startId);
  41. }
  42. @Override
  43. public void onDestroy() {
  44. super.onDestroy();
  45. // Service被终止的同时也停止定时器继续运行
  46. timer.cancel();
  47. timer = null;
  48. }
  49. private class RefreshTask extends TimerTask {
  50. @Override
  51. public void run() {
  52. // 当前界面没有悬浮窗显示,则创建悬浮
  53. if (!FloatWindowManager.getInstance(context).isWindowShowing()) {
  54. handler.post(new Runnable() {
  55. @Override
  56. public void run() {
  57. FloatWindowManager.getInstance(context)
  58. .createSmallWindow(context, layoutResId,
  59. rootLayoutId);
  60. }
  61. });
  62. }
  63. }
  64. }
  65. @Override
  66. public IBinder onBind(Intent intent) {
  67. return null;
  68. }
  69. }

除了后台服务之外,我们还需要两个自定义的布局,分别是FloatWindowSmallView和FloatWindowBigView,这两个自定义的布局,主要负责悬浮窗的前台显示,我们分别看一下代码实现。

首先是FloatWindowSmallView类的实现。

[java] view plain copy  
  1. package com.qust.floatwindow;
  2. import java.lang.reflect.Field;
  3. import android.annotation.SuppressLint;
  4. import android.content.Context;
  5. import android.graphics.PixelFormat;
  6. import android.view.Gravity;
  7. import android.view.LayoutInflater;
  8. import android.view.MotionEvent;
  9. import android.view.View;
  10. import android.view.WindowManager;
  11. import android.widget.LinearLayout;
  12. import android.widget.TextView;
  13. import com.qust.demo.ScreenUtils;
  14. import com.qust.floatingwindow.R;
  15. /**
  16. * 小悬浮窗,用于初始显示
  17. *
  18. * @author zhaokaiqiang
  19. *
  20. */
  21. public class FloatWindowSmallView extends LinearLayout {
  22. // 小悬浮窗的宽
  23. public int viewWidth;
  24. // 小悬浮窗的高
  25. public int viewHeight;
  26. // 系统状态栏的高度
  27. private static int statusBarHeight;
  28. // 用于更新小悬浮窗的位置
  29. private WindowManager windowManager;
  30. // 小悬浮窗的布局参数
  31. public WindowManager.LayoutParams smallWindowParams;
  32. // 记录当前手指位置在屏幕上的横坐标
  33. private float xInScreen;
  34. // 记录当前手指位置在屏幕上的纵坐标
  35. private float yInScreen;
  36. // 记录手指按下时在屏幕上的横坐标,用来判断单击事件
  37. private float xDownInScreen;
  38. // 记录手指按下时在屏幕上的纵坐标,用来判断单击事件
  39. private float yDownInScreen;
  40. // 记录手指按下时在小悬浮窗的View上的横坐标
  41. private float xInView;
  42. // 记录手指按下时在小悬浮窗的View上的纵坐标
  43. private float yInView;
  44. // 单击接口
  45. private OnClickListener listener;
  46. /**
  47. * 构造函数
  48. *
  49. * @param context
  50. *            上下文对象
  51. * @param layoutResId
  52. *            布局资源id
  53. * @param rootLayoutId
  54. *            根布局id
  55. */
  56. public FloatWindowSmallView(Context context, int layoutResId,
  57. int rootLayoutId) {
  58. super(context);
  59. windowManager = (WindowManager) context
  60. .getSystemService(Context.WINDOW_SERVICE);
  61. LayoutInflater.from(context).inflate(layoutResId, this);
  62. View view = findViewById(rootLayoutId);
  63. viewWidth = view.getLayoutParams().width;
  64. viewHeight = view.getLayoutParams().height;
  65. statusBarHeight = getStatusBarHeight();
  66. TextView percentView = (TextView) findViewById(R.id.percent);
  67. percentView.setText("悬浮窗");
  68. smallWindowParams = new WindowManager.LayoutParams();
  69. // 设置显示类型为phone
  70. smallWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
  71. // 显示图片格式
  72. smallWindowParams.format = PixelFormat.RGBA_8888;
  73. // 设置交互模式
  74. smallWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  75. | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  76. // 设置对齐方式为左上
  77. smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
  78. smallWindowParams.width = viewWidth;
  79. smallWindowParams.height = viewHeight;
  80. smallWindowParams.x = ScreenUtils.getScreenWidth(context);
  81. smallWindowParams.y = ScreenUtils.getScreenHeight(context) / 2;
  82. }
  83. @SuppressLint("ClickableViewAccessibility")
  84. @Override
  85. public boolean onTouchEvent(MotionEvent event) {
  86. switch (event.getAction()) {
  87. // 手指按下时记录必要的数据,纵坐标的值都减去状态栏的高度
  88. case MotionEvent.ACTION_DOWN:
  89. // 获取相对与小悬浮窗的坐标
  90. xInView = event.getX();
  91. yInView = event.getY();
  92. // 按下时的坐标位置,只记录一次
  93. xDownInScreen = event.getRawX();
  94. yDownInScreen = event.getRawY() - statusBarHeight;
  95. break;
  96. case MotionEvent.ACTION_MOVE:
  97. // 时时的更新当前手指在屏幕上的位置
  98. xInScreen = event.getRawX();
  99. yInScreen = event.getRawY() - statusBarHeight;
  100. // 手指移动的时候更新小悬浮窗的位置
  101. updateViewPosition();
  102. break;
  103. case MotionEvent.ACTION_UP:
  104. // 如果手指离开屏幕时,按下坐标与当前坐标相等,则视为触发了单击事件
  105. if (xDownInScreen == event.getRawX()
  106. && yDownInScreen == (event.getRawY() - getStatusBarHeight())) {
  107. if (listener != null) {
  108. listener.click();
  109. }
  110. }
  111. break;
  112. }
  113. return true;
  114. }
  115. /**
  116. * 设置单击事件的回调接口
  117. */
  118. public void setOnClickListener(OnClickListener listener) {
  119. this.listener = listener;
  120. }
  121. /**
  122. * 更新小悬浮窗在屏幕中的位置
  123. */
  124. private void updateViewPosition() {
  125. smallWindowParams.x = (int) (xInScreen - xInView);
  126. smallWindowParams.y = (int) (yInScreen - yInView);
  127. windowManager.updateViewLayout(this, smallWindowParams);
  128. }
  129. /**
  130. * 获取状态栏的高度
  131. *
  132. * @return
  133. */
  134. private int getStatusBarHeight() {
  135. try {
  136. Class<?> c = Class.forName("com.android.internal.R$dimen");
  137. Object o = c.newInstance();
  138. Field field = c.getField("status_bar_height");
  139. int x = (Integer) field.get(o);
  140. return getResources().getDimensionPixelSize(x);
  141. } catch (Exception e) {
  142. e.printStackTrace();
  143. }
  144. return 0;
  145. }
  146. /**
  147. * 单击接口
  148. *
  149. * @author zhaokaiqiang
  150. *
  151. */
  152. public interface OnClickListener {
  153. public void click();
  154. }
  155. }

在这个类里面,主要的工作是实现悬浮窗口在桌面前端的实现,还有就是位置的移动和单击事件的判断以及处理。这里使用的是主要是WindowManager类的一些方法和属性,下一篇会详细说明,这篇只说实现。

除了小悬浮窗之外,点击之后弹出的二级悬浮窗也是类似的方式添加到桌面上,下面是二级悬浮窗的代码。

[java] view plain copy  
  1. package com.qust.floatwindow;
  2. import android.content.Context;
  3. import android.graphics.PixelFormat;
  4. import android.view.Gravity;
  5. import android.view.LayoutInflater;
  6. import android.view.View;
  7. import android.view.WindowManager;
  8. import android.widget.LinearLayout;
  9. import android.widget.TextView;
  10. import com.qust.demo.ScreenUtils;
  11. import com.qust.floatingwindow.R;
  12. public class FloatWindowBigView extends LinearLayout {
  13. // 记录大悬浮窗的宽
  14. public int viewWidth;
  15. // 记录大悬浮窗的高
  16. public int viewHeight;
  17. public WindowManager.LayoutParams bigWindowParams;
  18. private Context context;
  19. public FloatWindowBigView(Context context) {
  20. super(context);
  21. this.context = context;
  22. LayoutInflater.from(context).inflate(R.layout.float_window_big, this);
  23. View view = findViewById(R.id.big_window_layout);
  24. viewWidth = view.getLayoutParams().width;
  25. viewHeight = view.getLayoutParams().height;
  26. bigWindowParams = new WindowManager.LayoutParams();
  27. // 设置显示的位置,默认的是屏幕中心
  28. bigWindowParams.x = ScreenUtils.getScreenWidth(context) / 2 - viewWidth
  29. / 2;
  30. bigWindowParams.y = ScreenUtils.getScreenHeight(context) / 2
  31. - viewHeight / 2;
  32. bigWindowParams.type = WindowManager.LayoutParams.TYPE_PHONE;
  33. bigWindowParams.format = PixelFormat.RGBA_8888;
  34. // 设置交互模式
  35. bigWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  36. | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  37. bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
  38. bigWindowParams.width = viewWidth;
  39. bigWindowParams.height = viewHeight;
  40. initView();
  41. }
  42. private void initView() {
  43. TextView tv_back = (TextView) findViewById(R.id.tv_back);
  44. tv_back.setOnClickListener(new OnClickListener() {
  45. @Override
  46. public void onClick(View v) {
  47. FloatWindowManager.getInstance(context).removeBigWindow();
  48. }
  49. });
  50. }
  51. }

这些基本的类建立起来之后,剩下的就是最重要的类FloatWindowManager的实现。这个类实现的就是对悬浮窗的操作。

[java] view plain copy  
  1. package com.qust.floatwindow;
  2. import android.content.Context;
  3. import android.content.Intent;
  4. import android.view.WindowManager;
  5. /**
  6. * 悬浮窗管理器
  7. *
  8. * @author zhaokaiqiang
  9. *
  10. */
  11. public class FloatWindowManager {
  12. // 小悬浮窗对象
  13. private FloatWindowSmallView smallWindow;
  14. // 大悬浮窗对象
  15. private FloatWindowBigView bigWindow;
  16. // 用于控制在屏幕上添加或移除悬浮窗
  17. private WindowManager mWindowManager;
  18. // FloatWindowManager的单例
  19. private static FloatWindowManager floatWindowManager;
  20. // 上下文对象
  21. private Context context;
  22. private FloatWindowManager(Context context) {
  23. this.context = context;
  24. }
  25. public static FloatWindowManager getInstance(Context context) {
  26. if (floatWindowManager == null) {
  27. floatWindowManager = new FloatWindowManager(context);
  28. }
  29. return floatWindowManager;
  30. }
  31. /**
  32. * 创建小悬浮窗
  33. *
  34. * @param context
  35. *            必须为应用程序的Context.
  36. */
  37. public void createSmallWindow(Context context, int layoutResId,
  38. int rootLayoutId) {
  39. WindowManager windowManager = getWindowManager();
  40. if (smallWindow == null) {
  41. smallWindow = new FloatWindowSmallView(context, layoutResId,
  42. rootLayoutId);
  43. windowManager.addView(smallWindow, smallWindow.smallWindowParams);
  44. }
  45. }
  46. /**
  47. * 将小悬浮窗从屏幕上移除
  48. *
  49. * @param context
  50. */
  51. public void removeSmallWindow() {
  52. if (smallWindow != null) {
  53. WindowManager windowManager = getWindowManager();
  54. windowManager.removeView(smallWindow);
  55. smallWindow = null;
  56. }
  57. }
  58. public void setOnClickListener(FloatWindowSmallView.OnClickListener listener) {
  59. if (smallWindow != null) {
  60. smallWindow.setOnClickListener(listener);
  61. }
  62. }
  63. /**
  64. * 创建大悬浮窗
  65. *
  66. * @param context
  67. *            必须为应用程序的Context.
  68. */
  69. public void createBigWindow(Context context) {
  70. WindowManager windowManager = getWindowManager();
  71. if (bigWindow == null) {
  72. bigWindow = new FloatWindowBigView(context);
  73. windowManager.addView(bigWindow, bigWindow.bigWindowParams);
  74. }
  75. }
  76. /**
  77. * 将大悬浮窗从屏幕上移除
  78. *
  79. * @param context
  80. */
  81. public void removeBigWindow() {
  82. if (bigWindow != null) {
  83. WindowManager windowManager = getWindowManager();
  84. windowManager.removeView(bigWindow);
  85. bigWindow = null;
  86. }
  87. }
  88. public void removeAll() {
  89. context.stopService(new Intent(context, FloatWindowService.class));
  90. removeSmallWindow();
  91. removeBigWindow();
  92. }
  93. /**
  94. * 是否有悬浮窗显示(包括小悬浮窗和大悬浮)
  95. *
  96. * @return 有悬浮窗显示在桌面上返回true,没有的话返回false
  97. */
  98. public boolean isWindowShowing() {
  99. return smallWindow != null || bigWindow != null;
  100. }
  101. /**
  102. * 如果WindowManager还未创建,则创建新的WindowManager返回。否则返回当前已创建的WindowManager
  103. *
  104. * @param context
  105. * @return
  106. */
  107. private WindowManager getWindowManager() {
  108. if (mWindowManager == null) {
  109. mWindowManager = (WindowManager) context
  110. .getSystemService(Context.WINDOW_SERVICE);
  111. }
  112. return mWindowManager;
  113. }
  114. }

还有个获取屏幕宽高的帮助类。

[java] view plain copy  
  1. package com.qust.demo;
  2. import android.content.Context;
  3. import android.view.WindowManager;
  4. /**
  5. * 屏幕帮助类
  6. *
  7. * @author zhaokaiqiang
  8. *
  9. */
  10. public class ScreenUtils {
  11. /**
  12. * 获取屏幕宽度
  13. *
  14. * @return
  15. */
  16. @SuppressWarnings("deprecation")
  17. public static int getScreenWidth(Context context) {
  18. return ((WindowManager) context
  19. .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
  20. .getWidth();
  21. }
  22. /**
  23. * 获取屏幕宽度
  24. *
  25. * @return
  26. */
  27. @SuppressWarnings("deprecation")
  28. public static int getScreenHeight(Context context) {
  29. return ((WindowManager) context
  30. .getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
  31. .getHeight();
  32. }
  33. }

完成这些,我们就可以直接用了。

[java] view plain copy  
  1. package com.qust.demo;
  2. import android.app.Activity;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.os.Bundle;
  6. import android.view.KeyEvent;
  7. import android.view.View;
  8. import com.qust.floatingwindow.R;
  9. import com.qust.floatwindow.FloatWindowManager;
  10. import com.qust.floatwindow.FloatWindowService;
  11. import com.qust.floatwindow.FloatWindowSmallView.OnClickListener;
  12. /**
  13. * 示例
  14. *
  15. * @ClassName: com.qust.demo.MainActivity
  16. * @Description:
  17. * @author zhaokaiqiang
  18. * @date 2014-10-23 下午11:30:13
  19. *
  20. */
  21. public class MainActivity extends Activity {
  22. private FloatWindowManager floatWindowManager;
  23. private Context context;
  24. @Override
  25. protected void onCreate(Bundle savedInstanceState) {
  26. super.onCreate(savedInstanceState);
  27. setContentView(R.layout.activity_main);
  28. context = this;
  29. floatWindowManager = FloatWindowManager.getInstance(context);
  30. }
  31. /**
  32. * 显示小窗口
  33. *
  34. * @param view
  35. */
  36. public void show(View view) {
  37. // 需要传递小悬浮窗布局,以及根布局的id,启动后台服务
  38. Intent intent = new Intent(context, FloatWindowService.class);
  39. intent.putExtra(FloatWindowService.LAYOUT_RES_ID,
  40. R.layout.float_window_small);
  41. intent.putExtra(FloatWindowService.ROOT_LAYOUT_ID,
  42. R.id.small_window_layout);
  43. startService(intent);
  44. }
  45. /**
  46. * 显示二级悬浮窗
  47. *
  48. * @param view
  49. */
  50. public void showBig(View view) {
  51. // 设置小悬浮窗的单击事件
  52. floatWindowManager.setOnClickListener(new OnClickListener() {
  53. @Override
  54. public void click() {
  55. floatWindowManager.createBigWindow(context);
  56. }
  57. });
  58. }
  59. /**
  60. * 移除所有的悬浮窗
  61. *
  62. * @param view
  63. */
  64. public void remove(View view) {
  65. floatWindowManager.removeAll();
  66. }
  67. @Override
  68. public boolean onKeyDown(int keyCode, KeyEvent event) {
  69. // 返回键移除二级悬浮窗
  70. if (keyCode == KeyEvent.KEYCODE_BACK
  71. && event.getAction() == KeyEvent.ACTION_DOWN) {
  72. floatWindowManager.removeBigWindow();
  73. return true;
  74. }
  75. return super.onKeyDown(keyCode, event);
  76. }
  77. }

项目下载地址:https://github.com/ZhaoKaiQiang/FloatWindow

通用的桌面悬浮窗口的实现相关推荐

  1. 【Anroid界面实现】通用的桌面悬浮窗口的实现

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一 ...

  2. Android桌面悬浮窗口举例

    概述 Android项目开发时,有时候需要开发一些悬浮在桌面上的视图.比如桌面小精灵,各种音乐播放器的悬浮播放控制栏等等.本文就借助一个小的demo,用代码的方式大概进行介绍. 原理 开发桌面悬浮窗口 ...

  3. 桌面悬浮窗口(可拖动)

    一.开发前原理简述 桌面悬浮窗口,(如360的清理加速等悬浮按钮),调用WindowManager,并设置WindowManager.LayoutParams的相关属性,通过WindowManager ...

  4. android 添加随意拖动的桌面悬浮窗口,android 添加随意拖动的桌面悬浮窗口

    用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧? 其实实现这种功能,主要有两步: 1.判断当前显示的是为桌面.这个内容我在前面的帖子里面已经有过介绍, ...

  5. android多个悬浮窗口,android 添加随意拖动的桌面悬浮窗口

    用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧? 其实实现这种功能,主要有两步: 1.判断当前显示的是为桌面.这个内容我在前面的帖子里面已经有过介绍, ...

  6. android自动悬浮窗代码,Android_Android实现桌面悬浮窗、蒙板效果实例代码,现在很多安全类的软件,比如3 - phpStudy...

    Android实现桌面悬浮窗.蒙板效果实例代码 现在很多安全类的软件,比如360手机助手,百度手机助手等等,都有一个悬浮窗,可以飘浮在桌面上,方便用户使用一些常用的操作. 今天这篇文章,就是介绍如何实 ...

  7. android视频聊天桌面小窗口怎么实现,android视频通话悬浮窗的适配

    前序 按项目交互要求,需要把视频通话界面,缩小至悬浮窗显示,基本实现思路这个比较好想,就是启用一个service,在里面用WindowManager去addView来展示悬浮窗画面.基本效果是有了,但 ...

  8. android实现 桌面移动悬浮窗口实现

    现在很多应用都有这样的功能,比如360等安全卫士,手机管家之内的应用. 效果图: 一.实现原理及移动思路 调用WindowManager,并设置WindowManager.LayoutParams的相 ...

  9. android多个悬浮窗口的实现,android实现桌面移动悬浮窗口

    现在很多应用都有这样的功能,比如360等安全卫士,手机管家之内的应用. 效果图: 一.实现原理及移动思路 调用WindowManager,并设置WindowManager.LayoutParams的相 ...

最新文章

  1. C++:随笔3--复杂的数据结构
  2. s120面板控制调速_SINAMICS S120变频调速装置
  3. oracle虚拟机 centos6.5,虚拟机oracle virtualbox 上安装centos6.5 网络设置
  4. 分享:一个基于NPOI的excel导入导出组件(强类型)
  5. fluent python 第二版_Fluent Python 笔记(二):序列基础
  6. 封装的ADO.NET对数据库操作经典类
  7. Spark中的数据本地性
  8. Bochs 调试命令
  9. koa访问mysql数据库操作
  10. TListView列表拒绝添加重复信息
  11. 全球编程厉害的14位大佬
  12. 初尝Mcafee之CEE企业版概述【01】
  13. 微信自动回复机器人使用教程
  14. matlab 判断矩形相交,如何在matlab中获取线矩形交叉段
  15. 英文版软件工程试题模拟试题
  16. 如何在vue中优雅的使用ocx控件:控件引用
  17. c++#学生平均成绩,学号排序
  18. dell最新计算机如何U盘引导,2018戴尔最新版电脑bios设置u盘启动教程
  19. 智能家居DIY创意之智能灯泡
  20. 从小镇到北大!再到阿里达摩院,「AI萝莉」的“升级打怪”之路...

热门文章

  1. phpcms自定义表单
  2. C语言轮班,教你如何做好呼叫中心排班管理
  3. 仿电影天堂苹果CMS模板苹果cms电影天堂模板
  4. java的接口简单使用---interface
  5. java jtextfield 居中_java – 如何使JTextfield居中
  6. 使用安富莱harldfault调试方法总结
  7. 航天信息将积极转型应对机遇
  8. 西门子 S7-200 通过模块连接 Kepware OPC 通讯方法
  9. 我的世界服务器物品整理机,我的世界快速整理箱子 自动分拣机
  10. 如何在iPhone或iPad上设置动态壁纸