Android悬浮窗实现 使用WindowManager

Android悬浮窗实现 使用WindowManager

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

点击和按键事件

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

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

  布局如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout 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 
10     <RelativeLayout
11         android:id="@+id/popup_window"
12         android:layout_width="@dimen/dialog_window_width"
13         android:layout_height="@dimen/dialog_window_height"
14         android:background="@color/white"
15         android:orientation="vertical" >
16 
17         <TextView
18             android:id="@+id/title"
19             android:layout_width="match_parent"
20             android:layout_height="@dimen/dialog_title_height"
21             android:gravity="center"
22             android:text="@string/default_title"
23             android:textColor="@color/dialog_title_text_color"
24             android:textSize="@dimen/dialog_title_text_size" />
25 
26         <View
27             android:id="@+id/title_divider"
28             android:layout_width="match_parent"
29             android:layout_height="2dp"
30             android:layout_below="@id/title"
31             android:background="@drawable/dialog_title_divider" />
32 
33         <TextView
34             android:id="@+id/content"
35             android:layout_width="match_parent"
36             android:layout_height="wrap_content"
37             android:layout_below="@id/title_divider"
38             android:gravity="center"
39             android:padding="@dimen/dialog_content_padding_side"
40             android:text="@string/default_content"
41             android:textColor="@color/dialog_content_text_color"
42             android:textSize="@dimen/dialog_content_text_size" />
43 
44         <LinearLayout
45             android:layout_width="match_parent"
46             android:layout_height="wrap_content"
47             android:layout_alignParentBottom="true"
48             android:orientation="horizontal"
49             android:paddingBottom="@dimen/dialog_content_padding_bottom"
50             android:paddingLeft="@dimen/dialog_content_padding_side"
51             android:paddingRight="@dimen/dialog_content_padding_side" >
52 
53             <Button
54                 android:id="@+id/negativeBtn"
55                 android:layout_width="wrap_content"
56                 android:layout_height="wrap_content"
57                 android:layout_weight="1"
58                 android:background="@drawable/promote_window_negative_btn_selector"
59                 android:focusable="true"
60                 android:padding="@dimen/dialog_button_padding"
61                 android:text="@string/default_btn_cancel"
62                 android:textColor="@color/dialog_negative_btn_text_color"
63                 android:textSize="@dimen/dialog_button_text_size" />
64 
65             <Button
66                 android:id="@+id/positiveBtn"
67                 android:layout_width="wrap_content"
68                 android:layout_height="wrap_content"
69                 android:layout_marginLeft="18dp"
70                 android:layout_weight="1"
71                 android:background="@drawable/promote_window_positive_btn_selector"
72                 android:focusable="true"
73                 android:padding="@dimen/dialog_button_padding"
74                 android:text="@string/default_btn_ok"
75                 android:textColor="@color/dialog_positive_btn_text_color"
76                 android:textSize="@dimen/dialog_button_text_size" />
77         </LinearLayout>
78     </RelativeLayout>
79 
80 </LinearLayout>
81 
82 popupwindow.xml

点击外部可消除设置:

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

点击Back键可隐藏弹窗:

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

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

完整效果

  完整代码:

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


参考资料

  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

原文地址:https://www.cnblogs.com/zl1991/p/5122388.html