Android 使用WindowManager实现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中添加如下权限:
<!-- 显示顶层浮窗 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
注意:在MIUI上需要在设置中打开本应用的”显示悬浮窗”开关,并且重启应用,否则悬浮窗只能显示在本应用界面内,不能显示在手机桌面上。
服务获取和基本参数设置
// 获取应用的Context
mContext = context.getApplicationContext();
// 获取WindowManager
mWindowManager = (WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE);
参数设置:
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
// 类型
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
// WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
// 设置flag
int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
// | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
params.flags = flags;
// 不设置这个弹出框的透明遮罩显示为黑色
params.format = PixelFormat.TRANSLUCENT;
// FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
// 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
// 不设置这个flag的话,home页的划屏会有问题
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER;
点击和按键事件
除了View中的各个控件的点击事件之外,弹窗View的消失控制需要一些处理。
点击弹窗外部可隐藏弹窗的效果,首先,悬浮窗是全屏的,只不过最外层的是透明或者半透明的:
布局如下:
- popupwindow.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/darken_background"
android:gravity="center"
android:orientation="vertical" >
<RelativeLayout
android:id="@+id/popup_window"
android:layout_width="@dimen/dialog_window_width"
android:layout_height="@dimen/dialog_window_height"
android:background="@color/white"
android:orientation="vertical" >
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_title_height"
android:gravity="center"
android:text="@string/default_title"
android:textColor="@color/dialog_title_text_color"
android:textSize="@dimen/dialog_title_text_size" />
<View
android:id="@+id/title_divider"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_below="@id/title"
android:background="@drawable/dialog_title_divider" />
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/title_divider"
android:gravity="center"
android:padding="@dimen/dialog_content_padding_side"
android:text="@string/default_content"
android:textColor="@color/dialog_content_text_color"
android:textSize="@dimen/dialog_content_text_size" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:paddingBottom="@dimen/dialog_content_padding_bottom"
android:paddingLeft="@dimen/dialog_content_padding_side"
android:paddingRight="@dimen/dialog_content_padding_side" >
<Button
android:id="@+id/negativeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight=""
android:background="@drawable/promote_window_negative_btn_selector"
android:focusable="true"
android:padding="@dimen/dialog_button_padding"
android:text="@string/default_btn_cancel"
android:textColor="@color/dialog_negative_btn_text_color"
android:textSize="@dimen/dialog_button_text_size" />
<Button
android:id="@+id/positiveBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="18dp"
android:layout_weight=""
android:background="@drawable/promote_window_positive_btn_selector"
android:focusable="true"
android:padding="@dimen/dialog_button_padding"
android:text="@string/default_btn_ok"
android:textColor="@color/dialog_positive_btn_text_color"
android:textSize="@dimen/dialog_button_text_size" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
点击外部可消除设置:
// 点击窗口外部区域可消除
// 这点的实现主要将悬浮窗设置为全屏大小,外层有个透明背景,中间一部分视为内容区域
// 所以点击内容区域外部视为点击悬浮窗外部
final View popupWindowView = view.findViewById(R.id.popup_window);// 非透明的内容区域
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
LogUtil.i(LOG_TAG, "onTouch");
int x = (int) event.getX();
int y = (int) event.getY();
Rect rect = new Rect();
popupWindowView.getGlobalVisibleRect(rect);
if (!rect.contains(x, y)) {
WindowUtils.hidePopupWindow();
}
LogUtil.i(LOG_TAG, "onTouch : " + x + ", " + y + ", rect: "
+ rect);
return false;
}
});
点击Back键可隐藏弹窗:
注意Flag不能设置WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE。
// 点击back键可消除
view.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
WindowUtils.hidePopupWindow();
return true;
default:
return false;
}
}
});
完整效果
完整代码:
MainActivity
package com.example.hellowindow;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {
private Handler mHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler();
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
WindowUtils.showPopupWindow(MainActivity.this);
}
}, * );
}
});
}
}
WindowUtils
package com.example.hellowindow;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
/**
* 弹窗辅助类
*
* @ClassName WindowUtils
*
*
*/
public class WindowUtils {
private static final String LOG_TAG = "WindowUtils";
private static View mView = null;
private static WindowManager mWindowManager = null;
private static Context mContext = null;
public static Boolean isShown = false;
/**
* 显示弹出框
*
* @param context
* @param view
*/
public static void showPopupWindow(final Context context) {
if (isShown) {
LogUtil.i(LOG_TAG, "return cause already shown");
return;
}
isShown = true;
LogUtil.i(LOG_TAG, "showPopupWindow");
// 获取应用的Context
mContext = context.getApplicationContext();
// 获取WindowManager
mWindowManager = (WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE);
mView = setUpView(context);
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
// 类型
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
// WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
// 设置flag
int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
// | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
// 如果设置了WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,弹出的View收不到Back键的事件
params.flags = flags;
// 不设置这个弹出框的透明遮罩显示为黑色
params.format = PixelFormat.TRANSLUCENT;
// FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
// 设置 FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按
// 不设置这个flag的话,home页的划屏会有问题
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER;
mWindowManager.addView(mView, params);
LogUtil.i(LOG_TAG, "add view");
}
/**
* 隐藏弹出框
*/
public static void hidePopupWindow() {
LogUtil.i(LOG_TAG, "hide " + isShown + ", " + mView);
if (isShown && null != mView) {
LogUtil.i(LOG_TAG, "hidePopupWindow");
mWindowManager.removeView(mView);
isShown = false;
}
}
private static View setUpView(final Context context) {
LogUtil.i(LOG_TAG, "setUp view");
View view = LayoutInflater.from(context).inflate(R.layout.popupwindow,
null);
Button positiveBtn = (Button) view.findViewById(R.id.positiveBtn);
positiveBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtil.i(LOG_TAG, "ok on click");
// 打开安装包
// 隐藏弹窗
WindowUtils.hidePopupWindow();
}
});
Button negativeBtn = (Button) view.findViewById(R.id.negativeBtn);
negativeBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtil.i(LOG_TAG, "cancel on click");
WindowUtils.hidePopupWindow();
}
});
// 点击窗口外部区域可消除
// 这点的实现主要将悬浮窗设置为全屏大小,外层有个透明背景,中间一部分视为内容区域
// 所以点击内容区域外部视为点击悬浮窗外部
final View popupWindowView = view.findViewById(R.id.popup_window);// 非透明的内容区域
view.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
LogUtil.i(LOG_TAG, "onTouch");
int x = (int) event.getX();
int y = (int) event.getY();
Rect rect = new Rect();
popupWindowView.getGlobalVisibleRect(rect);
if (!rect.contains(x, y)) {
WindowUtils.hidePopupWindow();
}
LogUtil.i(LOG_TAG, "onTouch : " + x + ", " + y + ", rect: "
+ rect);
return false;
}
});
// 点击back键可消除
view.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
WindowUtils.hidePopupWindow();
return true;
default:
return false;
}
}
});
return view;
}
}
参考资料
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
Android 使用WindowManager实现Android悬浮窗的更多相关文章
- Android仿360手机卫士悬浮窗效果
请看下图: 首先是一个小的悬浮窗显示的是当前使用了百分之多少的内存,点击一下小悬浮窗,就会弹出一个大的悬浮窗,可以一键加速.好,我们现在就来模拟实现一下 ...
- android课程表控件、悬浮窗、Todo应用、MVP框架、Kotlin完整项目源码
Android精选源码 Android游戏2048 MVP Kotlin项目(RxJava+Rerotfit+OkHttp+Glide) Android基于自定义Span的富文本编辑器 android ...
- Android悬浮窗实现 使用WindowManager
Android悬浮窗实现 使用WindowManager WindowManager介绍 通过Context.getSystemService(Context.WINDOW_SERVICE)可以获得 ...
- Android WindowManager悬浮窗:不需要申请权限实现悬浮
Android WindowManager悬浮窗:不需要申请权限实现悬浮 附录文章1介绍了Android平台上的悬浮窗WindowManager,WindowManager悬浮窗可以悬浮在And ...
- Android WindowManager实现悬浮窗效果 (一)——与当前Activity绑定
最近有学生做毕业设计,想使用悬浮窗这种效果,其实很简单,我们可以通过系统服务WindowManager来实现此功能,本章我们来试验一下在当前Activity之上创建一个悬浮的view. 第一步:认识W ...
- Android 高仿UC浏览器监控剪切板弹出悬浮窗功能
UC浏览器应该是android手机里 最流行的浏览器之一了,他们有一个功能 相信大家都体验过,就是如果你复制了什么文字,(在其他app中 复制也有这个效果!,所以能猜到肯定是监控了剪切板),就会弹出一 ...
- ANDROID 通过监听来电去电,并弹出悬浮窗
要监听android打电话和接电话,有一种的是通过新建一个Receiver继承自BroadcastReceiver. 还有一种也可通过PhoneStateListener来实现.今天就说说后面一种,废 ...
- Android 任何位置的可移动悬浮窗
刚好要做这块的东西,所以网上翻了下资料,百度出来的基本上都是:默认起始位置左上角,还不能改动,一改动起始位置,第二次拖动就不正常了~~ 下面直接附上任意位置可拖动的源码(由于是demo写的比较乱): ...
- 【android】来电悬浮窗
先看下效果图 说下思路: 1:监听来电广播 2:根据来电号码,和本地数据库做匹配,有记录的,则提取出头像.名字.职位,生成悬浮窗 3:监听来电广播,如果当前行为是空闲的(没有任何通话行为),则删除掉悬 ...
随机推荐
- Java时间日期格式转换Date转String和String转Date
Java时间格式转换大全 import java.text.*; import java.util.Calendar; public class VeDate { /** * 获取现在时间 * * @ ...
- python 各个地方导航(方便查询,持续更新!)
老男孩python全栈开发教程,武沛齐老师的知识点!:戳这里>>> 老男孩python全栈开发教程,linhaifeng老师的知识点!:戳这里>>> 老男孩pyth ...
- __int128的实现
#include<bitset> #include<algorithm> #include<iostream> #include<string> #in ...
- (C/C++学习)2.C语言中文件流操作基本函数总结
函数所在头文件:stdio.h 说明:前半部分主要为对各个文件流操作函数的例举,后半部分着重于上机运行分析.文中部分引用自王桂林老师的C/C++课件. 1.FIELE *fopen(const cha ...
- 洛谷P1616 疯狂的采药【完全背包】
题目描述 LiYuxiang是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师.为此,他想拜附近最有威望的医师为师.医师为了判断他的资质,给他出了一个难题.医师把他带到一个到处都是草药的山洞里对他说 ...
- CentOS7 笔记 (一) .NETCore
安装系统CentOS,虚拟机好麻烦,直接在阿里云开了一个6个月免费的ECS. 熟悉Linux 基本命令 登录,exit,vi ,vim,vi保存关闭,w,ls,mkdir,df,ip addr,修改系 ...
- Oracle SQL语句之常见优化方法总结
1.用EXISTS替换DISTINCT 当SQL包含一对多表查询时,避免在SELECT子句中使用DISTINCT,一般用EXIST替换,EXISTS(低效): SELECT DISTINCT USER ...
- Linux—Ubuntu14.0.5设置MySQL的字符集
1.mysql配置文件地址 cd /etc/mysql/my.cnf 2.在[mysqld]在下方添加以下代码 [mysqld] init_connect='SET collation_connect ...
- C#中三种弹出信息窗口的方式
弹出信息框,是浏览器客户端的事件.服务器没有弹出信息框的功能. 方法一: asp.net页面如果需要弹出信息框,则需要在前台页面上注册一个javascript脚本,使用alert方法.使用Client ...
- orcale 基本查询(1)
orcale 基本查询 查询当前用户下的所有表: select * from tab; 查询表结构: desc 表名设置行宽: set linesize 120;设置列宽: 数值类型: col 列名 ...