悬浮窗(Floating Window)是一种常见的应用场景,如视频播放器、弹幕、游戏手柄等等。在Android系统上,悬浮窗主要是通过系统级窗口实现的。本文将详细介绍如何使用Android系统提供的API开发一个悬浮窗app。
1. 悬浮窗权限
在Android 6.0及以上版本中,悬浮窗权限成为了敏感权限。开发者需要动态申请悬浮窗权限,否则会抛出异常。申请悬浮窗权限可以通过以下代码实现:
```java
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + context.getPackageName()));
context.startActivity(intent);
}
```
需要注意的是,申请悬浮窗权限需要在AndroidManifest.xml文件中添加以下声明:
```xml
```
2. 创建系统级窗口
在Android系统中,系统级窗口是一种特殊的窗口,可以位于所有应用程序的窗口之上。使用系统级窗口实现悬浮窗的步骤如下:
1. 创建WindowManager.LayoutParams
```java
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
layoutParams.format = PixelFormat.TRANSLUCENT;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.gravity = Gravity.START | Gravity.TOP;
```
在创建WindowManager.LayoutParams时,需要注意以下几点:
- layoutParams.type必须设置为WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,否则无法显示在其他应用程序窗口之上。
- layoutParams.format需要设置为PixelFormat.TRANSLUCENT,以支持悬浮窗的透明度。
- layoutParams.flags必须设置为WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,以确保悬浮窗不会抢占焦点,并且可以显示在屏幕之上。
- layoutParams.width和layoutParams.height可以设置为固定值或者WrapContent,可以根据需要设置。
- layoutParams.gravity是悬浮窗在屏幕上的位置,可以设置为Gravity.START | Gravity.TOP等值。
2. 创建悬浮窗View
```java
View floatingView = LayoutInflater.from(context).inflate(R.layout.floating_view, null);
```
在创建悬浮窗View时,需要注意以下几点:
- View的布局需要适配悬浮窗,可以使用LinearLayout、FrameLayout等布局。
- 需要处理View上的交互事件,如点击、长按等。
3. 添加悬浮窗到WindowManager
```java
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(floatingView, layoutParams);
```
在添加悬浮窗到WindowManager时,需要注意以下几点:
- 添加悬浮窗需要获取WindowManager对象。
- 添加悬浮窗时需要传入创建的WindowManager.LayoutParams对象。
- 添加悬浮窗后,需要处理悬浮窗的生命周期,如窗口关闭、窗口位置变化等。
3. 悬浮窗生命周期
悬浮窗的生命周期需要与应用程序的生命周期进行对应,以确保悬浮窗的正常运行。悬浮窗的生命周期如下:
1. 显示窗口
```java
public void showFloatingView(Context context) {
if(!isFloatingViewVisible) {
// 显示悬浮窗
createFloatingView(context);
// 设置悬浮窗View的交互事件
setFloatingViewOnClickListener(context);
// 设置悬浮窗View的生命周期回调
setFloatingViewLifecycleCallback(context);
isFloatingViewVisible = true;
}
}
```
2. 关闭窗口
```java
public void hideFloatingView(Context context) {
if(isFloatingViewVisible) {
// 隐藏悬浮窗
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.removeViewImmediate(floatingView);
floatingView = null;
isFloatingViewVisible = false;
}
}
```
3. 处理悬浮窗位置变化
```java
private WindowManager.LayoutParams layoutParams;
View.OnTouchListener floatingViewOnTouchListener = new View.OnTouchListener() {
private float lastX, lastY;
private int initialX, initialY;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 获取悬浮窗View的初始位置
initialX = layoutParams.x;
initialY = layoutParams.y;
lastX = event.getRawX();
lastY = event.getRawY();
return true;
case MotionEvent.ACTION_MOVE:
// 计算悬浮窗View的移动距离
int deltaX = (int) (event.getRawX() - lastX);
int deltaY = (int) (event.getRawY() - lastY);
// 更新悬浮窗View的位置
layoutParams.x = initialX + deltaX;
layoutParams.y = initialY + deltaY;
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.updateViewLayout(floatingView, layoutParams);
return true;
}
return false;
}
};
```
4. 处理悬浮窗生命周期回调
```java
private WindowManager.LayoutParams layoutParams;
WindowManager.BadTokenExceptionCallback floatingViewCallback = new WindowManager.BadTokenExceptionCallback() {
@Override
public void onBadTokenException(WindowManager parent, RuntimeException e) {
parent.removeView(floatingView);
hideFloatingView(context);
}
};
ViewTreeObserver.OnGlobalLayoutListener floatingViewLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if(floatingView == null) {
return;
}
layoutParams.width = floatingView.getWidth();
layoutParams.height = floatingView.getHeight();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.updateViewLayout(floatingView, layoutParams);
}
};
```
需要注意的是,处理悬浮窗的生命周期时,需要获取WindowManager.LayoutParams对象以及悬浮窗View对象,以确保能够正确地处理悬浮窗。
4. 总结
开发一个Android悬浮窗应用需要掌握系统级窗口的使用方法,了解悬浮窗的生命周期以及处理交互事件。通过此文的介绍,相信读者已经对Android悬浮窗开发有了深入了解,并且可以通过系统提供的API实现一个悬浮窗应用。