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://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