使用WindowManager实现Android悬浮窗

来源 http://www.cnblogs.com/mengdd/p/3824782.html

编辑推荐:稀土掘金,这是一个针对技术开发者的一个应用,你可以在掘金上获取最新最优质的技术干货,不仅仅是Android知识、前端、后端以至于产品和设计都有涉猎,想成为全栈工程师的朋友不要错过!

WindowManager介绍

  通过Context.getSystemService(Context.WINDOW_SERVICE)可以获得 WindowManager对象。

  每一个WindowManager对象都和一个特定的 Display绑定。

  想要获取一个不同的display的WindowManager,可以用 createDisplayContext(Display)来获取那个display的 Context,之后再使用:

Context.getSystemService(Context.WINDOW_SERVICE)来获取WindowManager。


  使用WindowManager可以在其他应用最上层,甚至手机桌面最上层显示窗口。

  调用的是WindowManager继承自基类的addView方法和removeView方法来显示和隐藏窗口。具体见后面的实例。


  另:API 17推出了Presentation,它将自动获取display的Context和WindowManager,可以方便地在另一个display上显示窗口。


WindowManager实现悬浮窗例子

声明权限

  首先在manifest中添加如下权限:

   
   
   
   
  1. android:name="android.permission.SYSTEM_ALERT_WINDOW" />

 注意:在MIUI上需要在设置中打开本应用的”显示悬浮窗”开关,并且重启应用,否则悬浮窗只能显示在本应用界面内,不能显示在手机桌面上。



服务获取和基本参数设置

   
   
   
   
  1. // 获取应用的Context
  2. mContext = context.getApplicationContext();
  3. // 获取WindowManager
  4. mWindowManager = (WindowManager) mContext
  5. .getSystemService(Context.WINDOW_SERVICE);


参数设置:

   
   
   
   
  1. final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
  2. // 类型
  3. params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
  4. // WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
  5. // 设置flag
  6. int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
  7. // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  8. // 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
  9. params.flags = flags;
  10. // 不设置这个弹出框的透明遮罩显示为黑色
  11. params.format = PixelFormat.TRANSLUCENT;
  12. // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
  13. // 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
  14. // 不设置这个flag的话,home页的划屏会有问题
  15. params.width = LayoutParams.MATCH_PARENT;
  16. params.height = LayoutParams.MATCH_PARENT;
  17. params.gravity = Gravity.CENTER;


点击和按键事件

  除了View中的各个控件的点击事件之外,弹窗View的消失控制需要一些处理。

  点击弹窗外部可隐藏弹窗的效果,首先,悬浮窗是全屏的,只不过最外层的是透明或者半透明的:

  布局如下:

   
   
   
   
  1. xml version="1.0" encoding="utf-8"?>
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:layout_gravity="center"
  6. android:background="@color/darken_background"
  7. android:gravity="center"
  8. android:orientation="vertical" >
  9. android:id="@+id/popup_window"
  10. android:layout_width="@dimen/dialog_window_width"
  11. android:layout_height="@dimen/dialog_window_height"
  12. android:background="@color/white"
  13. android:orientation="vertical" >
  14. android:id="@+id/title"
  15. android:layout_width="match_parent"
  16. android:layout_height="@dimen/dialog_title_height"
  17. android:gravity="center"
  18. android:text="@string/default_title"
  19. android:textColor="@color/dialog_title_text_color"
  20. android:textSize="@dimen/dialog_title_text_size" />
  21. android:id="@+id/title_divider"
  22. android:layout_width="match_parent"
  23. android:layout_height="2dp"
  24. android:layout_below="@id/title"
  25. android:background="@drawable/dialog_title_divider" />
  26. android:id="@+id/content"
  27. android:layout_width="match_parent"
  28. android:layout_height="wrap_content"
  29. android:layout_below="@id/title_divider"
  30. android:gravity="center"
  31. android:padding="@dimen/dialog_content_padding_side"
  32. android:text="@string/default_content"
  33. android:textColor="@color/dialog_content_text_color"
  34. android:textSize="@dimen/dialog_content_text_size" />
  35. android:layout_width="match_parent"
  36. android:layout_height="wrap_content"
  37. android:layout_alignParentBottom="true"
  38. android:orientation="horizontal"
  39. android:paddingBottom="@dimen/dialog_content_padding_bottom"
  40. android:paddingLeft="@dimen/dialog_content_padding_side"
  41. android:paddingRight="@dimen/dialog_content_padding_side" >
  42. android:id="@+id/negativeBtn"
  43. android:layout_width="wrap_content"
  44. android:layout_height="wrap_content"
  45. android:layout_weight="1"
  46. android:background="@drawable/promote_window_negative_btn_selector"
  47. android:focusable="true"
  48. android:padding="@dimen/dialog_button_padding"
  49. android:text="@string/default_btn_cancel"
  50. android:textColor="@color/dialog_negative_btn_text_color"
  51. android:textSize="@dimen/dialog_button_text_size" />
  52. android:id="@+id/positiveBtn"
  53. android:layout_width="wrap_content"
  54. android:layout_height="wrap_content"
  55. android:layout_marginLeft="18dp"
  56. android:layout_weight="1"
  57. android:background="@drawable/promote_window_positive_btn_selector"
  58. android:focusable="true"
  59. android:padding="@dimen/dialog_button_padding"
  60. android:text="@string/default_btn_ok"
  61. android:textColor="@color/dialog_positive_btn_text_color"
  62. android:textSize="@dimen/dialog_button_text_size" />
  63. popupwindow.xml


点击外部可消除设置:

   
   
   
   
  1. // 点击窗口外部区域可消除
  2. // 这点的实现主要将悬浮窗设置为全屏大小,外层有个透明背景,中间一部分视为内容区域
  3. // 所以点击内容区域外部视为点击悬浮窗外部
  4. final View popupWindowView = view.findViewById(R.id.popup_window);// 非透明的内容区域
  5. view.setOnTouchListener(new OnTouchListener() {
  6. @Override
  7. public boolean onTouch(View v, MotionEvent event) {
  8. LogUtil.i(LOG_TAG, "onTouch");
  9. int x = (int) event.getX();
  10. int y = (int) event.getY();
  11. Rect rect = new Rect();
  12. popupWindowView.getGlobalVisibleRect(rect);
  13. if (!rect.contains(x, y)) {
  14. WindowUtils.hidePopupWindow();
  15. }
  16. LogUtil.i(LOG_TAG, "onTouch : " + x + ", " + y + ", rect: "
  17. + rect);
  18. return false;
  19. }
  20. });


点击Back键可隐藏弹窗:

  注意Flag不能设置WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE。

   
   
   
   
  1. // 点击back键可消除
  2. view.setOnKeyListener(new OnKeyListener() {
  3. @Override
  4. public boolean onKey(View v, int keyCode, KeyEvent event) {
  5. switch (keyCode) {
  6. case KeyEvent.KEYCODE_BACK:
  7. WindowUtils.hidePopupWindow();
  8. return true;
  9. default:
  10. return false;
  11. }
  12. }
  13. });



完整效果


  完整代码:

MainActivity

   
   
   
   
  1. package com.example.hellowindow;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.os.Handler;
  5. import android.view.View;
  6. import android.view.View.OnClickListener;
  7. import android.widget.Button;
  8. public class MainActivity extends Activity {
  9. private Handler mHandler = null;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.activity_main);
  14. mHandler = new Handler();
  15. Button button = (Button) findViewById(R.id.button);
  16. button.setOnClickListener(new OnClickListener() {
  17. @Override
  18. public void onClick(View v) {
  19. mHandler.postDelayed(new Runnable() {
  20. @Override
  21. public void run() {
  22. WindowUtils.showPopupWindow(MainActivity.this);
  23. }
  24. }, 1000 * 3);
  25. }
  26. });
  27. }
  28. }


WindowUtils

   
   
   
   
  1. package com.example.hellowindow;
  2. import android.content.Context;
  3. import android.graphics.PixelFormat;
  4. import android.graphics.Rect;
  5. import android.view.Gravity;
  6. import android.view.KeyEvent;
  7. import android.view.LayoutInflater;
  8. import android.view.MotionEvent;
  9. import android.view.View;
  10. import android.view.View.OnKeyListener;
  11. import android.view.View.OnTouchListener;
  12. import android.view.WindowManager;
  13. import android.view.View.OnClickListener;
  14. import android.view.WindowManager.LayoutParams;
  15. import android.widget.Button;
  16. /**
  17. * 弹窗辅助类
  18. *
  19. * @ClassName WindowUtils
  20. *
  21. *
  22. */
  23. public class WindowUtils {
  24. private static final String LOG_TAG = "WindowUtils";
  25. private static View mView = null;
  26. private static WindowManager mWindowManager = null;
  27. private static Context mContext = null;
  28. public static Boolean isShown = false;
  29. /**
  30. * 显示弹出框
  31. *
  32. * @param context
  33. * @param view
  34. */
  35. public static void showPopupWindow(final Context context) {
  36. if (isShown) {
  37. LogUtil.i(LOG_TAG, "return cause already shown");
  38. return;
  39. }
  40. isShown = true;
  41. LogUtil.i(LOG_TAG, "showPopupWindow");
  42. // 获取应用的Context
  43. mContext = context.getApplicationContext();
  44. // 获取WindowManager
  45. mWindowManager = (WindowManager) mContext
  46. .getSystemService(Context.WINDOW_SERVICE);
  47. mView = setUpView(context);
  48. final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
  49. // 类型
  50. params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
  51. // WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
  52. // 设置flag
  53. int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
  54. // | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  55. // 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
  56. params.flags = flags;
  57. // 不设置这个弹出框的透明遮罩显示为黑色
  58. params.format = PixelFormat.TRANSLUCENT;
  59. // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
  60. // 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
  61. // 不设置这个flag的话,home页的划屏会有问题
  62. params.width = LayoutParams.MATCH_PARENT;
  63. params.height = LayoutParams.MATCH_PARENT;
  64. params.gravity = Gravity.CENTER;
  65. mWindowManager.addView(mView, params);
  66. LogUtil.i(LOG_TAG, "add view");
  67. }
  68. /**
  69. * 隐藏弹出框
  70. */
  71. public static void hidePopupWindow() {
  72. LogUtil.i(LOG_TAG, "hide " + isShown + ", " + mView);
  73. if (isShown && null != mView) {
  74. LogUtil.i(LOG_TAG, "hidePopupWindow");
  75. mWindowManager.removeView(mView);
  76. isShown = false;
  77. }
  78. }
  79. private static View setUpView(final Context context) {
  80. LogUtil.i(LOG_TAG, "setUp view");
  81. View view = LayoutInflater.from(context).inflate(R.layout.popupwindow,
  82. null);
  83. Button positiveBtn = (Button) view.findViewById(R.id.positiveBtn);
  84. positiveBtn.setOnClickListener(new OnClickListener() {
  85. @Override
  86. public void onClick(View v) {
  87. LogUtil.i(LOG_TAG, "ok on click");
  88. // 打开安装包
  89. // 隐藏弹窗
  90. WindowUtils.hidePopupWindow();
  91. }
  92. });
  93. Button negativeBtn = (Button) view.findViewById(R.id.negativeBtn);
  94. negativeBtn.setOnClickListener(new OnClickListener() {
  95. @Override
  96. public void onClick(View v) {
  97. LogUtil.i(LOG_TAG, "cancel on click");
  98. WindowUtils.hidePopupWindow();
  99. }
  100. });
  101. // 点击窗口外部区域可消除
  102. // 这点的实现主要将悬浮窗设置为全屏大小,外层有个透明背景,中间一部分视为内容区域
  103. // 所以点击内容区域外部视为点击悬浮窗外部
  104. final View popupWindowView = view.findViewById(R.id.popup_window);// 非透明的内容区域
  105. view.setOnTouchListener(new OnTouchListener() {
  106. @Override
  107. public boolean onTouch(View v, MotionEvent event) {
  108. LogUtil.i(LOG_TAG, "onTouch");
  109. int x = (int) event.getX();
  110. int y = (int) event.getY();
  111. Rect rect = new Rect();
  112. popupWindowView.getGlobalVisibleRect(rect);
  113. if (!rect.contains(x, y)) {
  114. WindowUtils.hidePopupWindow();
  115. }
  116. LogUtil.i(LOG_TAG, "onTouch : " + x + ", " + y + ", rect: "
  117. + rect);
  118. return false;
  119. }
  120. });
  121. // 点击back键可消除
  122. view.setOnKeyListener(new OnKeyListener() {
  123. @Override
  124. public boolean onKey(View v, int keyCode, KeyEvent event) {
  125. switch (keyCode) {
  126. case KeyEvent.KEYCODE_BACK:
  127. WindowUtils.hidePopupWindow();
  128. return true;
  129. default:
  130. return false;
  131. }
  132. }
  133. });
  134. return view;
  135. }
  136. }


参考资料

  WindowManager:

http://developer.android.com/reference/android/view/WindowManager.html

  参考实例:

http://blog.csdn.net/deng0zhaotai/article/details/16827719

http://www.xsmile.net/?p=538

http://blog.csdn.net/guolin_blog/article/details/8689140

  简单说明:

  Android之Window、WindowManager与窗口管理:

http://blog.csdn.net/xieqibao/article/details/6567814

  Android系统服务-WindowManager:

http://blog.csdn.net/chenyafei617/article/details/6577940

进一步的学习:

  老罗的Android之旅:

  Android Activity的窗口对象Window的创建过程分析:

http://blog.csdn.net/luoshengyang/article/details/8223770

  窗口管理服务WindowManagerService的简要介绍和学习计划:

http://blog.csdn.net/luoshengyang/article/details/8462738

  Android核心分析之窗口管理:

http://blog.csdn.net/maxleng/article/details/5557758

你可能感兴趣的:(android)