简介
我们在日常开发中,直接接触Window的机会并不多,充其量就是在使用悬浮窗这事情上。其实,Android中所有的视图呈现都是通过Window做到的,也就是说,除了悬浮窗外,像Activity、Dialog、Toast都是通过Window来呈现的。所以可以说,Window是View的直接管理者。要创建Window就要通过WindowManager类,它是外界访问Window的入口。今天我们就来通过创建一个悬浮窗的Demo来了解Window和WindowManager。
悬浮窗Demo
public class MainActivity extends Activity {
private WindowManager mWindowManager;
private WindowManager.LayoutParams mParams;
private View mFloatView;
private float mRawX;
private float mRawY;
private float mStartX;
private float mStartY;
private int mTitleHeight;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 6.0及以上系统,先跳至系统权限页开启"在其它应用上层显示"权限
if (Build.VERSION.SDK_INT >= 23) {
if(!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivityForResult(intent, 1);
return;
}
}
initView();
initfloat();
}
private int getTitleHeight() {
Rect frame = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
return statusBarHeight;
}
private void updateFloatWndPosition() {
mParams.x = (int)(mRawX - mStartX);
mParams.y = (int)(mRawY - mStartY);
mParams.gravity = Gravity.LEFT | Gravity. TOP;
mWindowManager.updateViewLayout(mFloatView, mParams);
}
private void initView() {
LayoutInflater lif = (LayoutInflater) (MainActivity.this).getSystemService(LAYOUT_INFLATER_SERVICE);
mFloatView = lif.inflate(R.layout.float_view, null);
mFloatView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (mTitleHeight == 0) {
mTitleHeight = getTitleHeight();
}
mRawX = event.getRawX();
mRawY = event.getRawY() - mTitleHeight;
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mStartX = event.getX();
mStartY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
updateFloatWndPosition();
break;
case MotionEvent.ACTION_UP:
updateFloatWndPosition();
break;
}
return true;
}
});
}
private void initfloat() {
if (mWindowManager != null) {
return;
}
mWindowManager = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
mParams = new WindowManager.LayoutParams();
mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
// WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
// WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
// WindowManager.LayoutParams.TYPE_TOAST;
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.format = PixelFormat.RGBA_8888; // PixelFormat.TRANSLUCENT 半透明
mParams.gravity = Gravity.CENTER; // Gravity.LEFT | Gravity. TOP;
// mParams.x = 100;
// mParams.y = 100;
mWindowManager.addView(mFloatView, mParams);
}
}
Window的类型
Window有三种类型,分别是:
应用Window 对应着一个Activity(层级范围是1~99)
子Window 不能单独存在,它需要附属在特定的父Window之中,比如Dialog(层级范围是1000~1999)
系统Window 系统Window是需要声明权限的,比如Toast和系统状态栏这些(范围是2000~2999)
应用Window包含以下几类:
定义 | 意义 |
FIRST_APPLICATION_WINDOW = 1 | 第一个普通应用窗口 |
TYPE_BASE_APPLICATION = 1 | 基窗口,所有其他类型的应用窗口将出现在基窗口上层 |
TYPE_APPLICATION = 2 | 普通应用窗口 |
TYPE_APPLICATION_STARTING = 3 | 应用程序启动时先显示此窗口,当真正的窗口配置完成后,此窗口被关闭 |
LAST_APPLICATION_WINDOW = 99 | 最后一个应用窗口 |
所有Activity默认的窗口类型都是TYPE_APPLICATION,在进行窗口叠加时,会动态改变应用窗口的层值,但层值不会大于99。
子Window包含以下几类:
定义 | 意义 |
FIRST_SUB_WINDOW = 1000 | 第一个子窗口 |
TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW | 应用窗口的子窗口,PopupWindow的默认类型 |
TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1 | 用来显示Media的窗口 |
TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2 | TYPE_APPLICATION_PANEL的子窗口 |
TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3 | OptionMenu、ContextMenu的默认类型 |
TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4 | TYPE_APPLICATION_MEDIA的重影窗口,显示在TYPE_APPLICATION_MEDIA和应用窗口之间 |
LAST_SUB_WINDOW = 1999 | 最后一个子窗口 |
创建子窗口时,客户端可以指定窗口类型介于1000-1999之间,在进行窗口叠加时,会动态调整层值。
系统Window有以下类型:
定义 | 意义 |
FIRST_SYSTEM_WINDOW = 2000 | 第一个系统窗口 |
TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW | 状态栏窗口 |
TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW +1 | 搜索条窗口 |
TYPE_PHONE = FIRST_SYSTEM_WINDOW + 2 | 来电显示窗口 |
TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW + 3 | 警告对话框 |
TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW + 4 | 屏保 |
TYPE_TOAST = FIRST_SYSTEM_WINDOW + 5 | Toast对应的窗口 |
TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 6 | 系统覆盖窗口,需要显示在所有窗口之上 |
TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW + 7 | 在屏幕保护下的来电显示窗口 |
TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW + 8 | 滑动状态条后出现的窗口 |
TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW + 9 | 屏保弹出的对话框 |
TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW + 10 | 系统错误窗口 |
TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW + 11 | 输入法窗口 |
TYPE_INPUT_METHOD_DIALOG = FIRST_SYSTEM_WINDOW + 12 | 输入法中备选框对应的窗口 |
TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW + 13 | 墙纸对应的窗口 |
TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW + 14 | 滑动状态条后出现的窗口 |
TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW + 15 | 安全系统覆盖窗口,显示在所有窗口之上。 |
…… | …… |
TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38 | 8.0新增,系统正式统一开发者使用悬浮窗的类型 |
LAST_SYSTEM_WINDOW = 2999 | 最后一个系统窗口 |
Window的属性
Window的属性,常用的选项:
FLAG_NOT_FOCUSABLE | Window不需要获取焦点,也不需要接收各种输入事件(收不到Back键的事件),此标记会同时启用FLAG_NOT_TOUCH_MODEAL,最终事件会直接传递给下层的具有焦点的Window。 |
FLAG_NOT_TOUCH_MODEAL | 收不到触屏事件,不会拦截其他Window的单击事件,一般情况下都需要开启 |
FLAG_SHOW_WHEN_LOCKED | 可让Window显示在锁屏的界面上 |
版本变动
在Android众多版本的迭代中,其实悬浮窗也有比较多的变化,所以我们在使用中还要针对情况来进行相应的适配。在5.1之前,大多数手机系统里只有声明了<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>权限有就可以在app中弹出悬浮窗,除了国内部分厂商手机如:小米、魅族等定制系统中处理了悬浮窗权限要开关。我们日常开发中,比较常用到的类型有:
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY | 没有实体,不会响应事件,无法进行交互,但可悬浮在锁屏面板 |
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT | 可以接收事件 |
WindowManager.LayoutParams.TYPE_TOAST | 在4.4以下无法接收事件,在4.4到7.0以下可以不加权限声明也能弹出悬浮窗在应用之上,在7.0及以上系统填补了不加权限声明的漏洞,在8.0中不能常显示,像正常Toast一样有显示的时间限制 |
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY | 8.0上指定开发者使用悬浮窗唯一的类型,其它类型都会报崩溃 |
在6.0及以上系统中,如果要使用悬浮窗,还必须要在系统设置中的“在其它应用上层显示”的权限开关勾选