Android之悬浮窗实现
文章目录
- 前言
- 一、效果图
- 二、实现步骤
- 1.AndroidManifest权限以及service注册
- 2.service代码
- 3.activity实现
- 总结
前言
经常接触音视频以及直播的同学应该知道,悬浮窗是必备需求,今天就记录一下自己悬浮窗的视线过程,流程就是点击缩小按钮回到桌面,把悬浮窗放桌面,点击悬浮窗回到应用界面。
一、效果图
效果图一张为正常界面,一张为悬浮窗效果界面。
二、实现步骤
1.AndroidManifest权限以及service注册
代码如下(示例):
<!--悬浮窗-->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 悬浮窗Service -->
<service
android:name=".service.FloatingWindowService"
android:enabled="true"
android:exported="false"/>
2.service代码
代码如下(示例):
/**
* @Author : CaoLiulang
* @Time : 2025/3/18 13:22
* @Description :悬浮窗service
*/
public class FloatingWindowService extends Service {
private WindowManager windowManager;
private View floatingView;
private ScheduledExecutorService executor;
// 定义变量记录初始触摸点和窗口初始位置
private float initialTouchX, initialTouchY;
private float startX; // 声明为成员变量
private float startY;
@Override
public void onCreate() {
super.onCreate();
executor = Executors.newSingleThreadScheduledExecutor();
createFloatingWindow();
}
private void createFloatingWindow() {
// 引入悬浮窗口布局
floatingView = LayoutInflater.from(this).inflate(R.layout.livestreamingconsolesx, null);
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
// 窗口参数设置
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
380, // 显式设置宽度(像素)
600, // 显式设置高度(像素)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY :
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.START | Gravity.TOP;
params.x = 20;
params.y = 20;
windowManager.addView(floatingView, params);
floatingView.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialTouchX = event.getRawX() - params.x;
initialTouchY = event.getRawY() - params.y;
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float newX = event.getRawX() - initialTouchX;
float newY = event.getRawY() - initialTouchY;
// 获取屏幕尺寸
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
if (wm != null) {
wm.getDefaultDisplay().getMetrics(metrics);
int screenWidth = metrics.widthPixels;
int screenHeight = metrics.heightPixels;
// 限制移动范围
newX = Math.max(0, Math.min(newX, screenWidth - params.width));
newY = Math.max(0, Math.min(newY, screenHeight - params.height));
}
params.x = (int) newX;
params.y = (int) newY;
windowManager.updateViewLayout(floatingView, params);
return true;
case MotionEvent.ACTION_UP:
// 计算移动距离
float deltaX = Math.abs(event.getRawX() - startX);
float deltaY = Math.abs(event.getRawY() - startY);
// 如果移动距离小于阈值,视为点击
if (deltaX < 5 && deltaY < 5) {
// 手动触发点击事件
floatingView.performClick();
}
return true;
}
return false;
});
// 单独设置 OnClickListener
floatingView.setOnClickListener(v -> {
// 点击逻辑 这里是跳转本软件,有的手机会重启,所以直接选择下面方式跳转当前界面
//Intent intent = getPackageManager().getLaunchIntentForPackage("这里就是自己的包名");
//if (intent != null) {
//intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// startActivity(intent);
//}
//跳转当前界面
Intent intent = new Intent(this, LiveStreamingConsole.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
//延迟一秒关闭
executor.schedule(() -> stopSelf(), 1, TimeUnit.SECONDS);
});
}
@Override
public void onDestroy() {
super.onDestroy();
// 关闭线程池(根据实际情况选择是否立即关闭)
executor.shutdownNow();
if (floatingView != null) windowManager.removeView(floatingView);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
3.activity实现
//定义变量
private var YOUR_REQUEST_CODE = 1000
//调用 动态判断权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
)
startActivityForResult(intent, YOUR_REQUEST_CODE)
} else {
createFloatingWindow();
}
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createFloatingWindow() {
//启动service
startService(Intent(this, FloatingWindowService::class.java))
// 返回桌面
moveTaskToBack(true)
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == YOUR_REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(
this
)
) {
createFloatingWindow()
} else {
Toast.makeText(this, "需要悬浮窗权限", Toast.LENGTH_SHORT).show()
}
}
}
总结
提示:service里面的布局文件就是悬浮窗的布局,所以xml我就没有贴代码,测试的时候可以自己随意画个xml看效果:
以上就是Android悬浮窗的实现了,service中还可以交互数据,也就是悬浮窗,欢迎各位同学指出问题。