安卓基础(悬浮窗分级菜单和弹窗)
initializeViews()
初始化
把全部的按钮都弄出来
// 主菜单按钮ImageButton mainButton = floatingMenuView.findViewById(R.id.main_button);// 二级菜单按钮subButtons = new ImageButton[3];subButtons[0] = floatingMenuView.findViewById(R.id.sub_button_1);subButtons[1] = floatingMenuView.findViewById(R.id.sub_button_2);subButtons[2] = floatingMenuView.findViewById(R.id.sub_button_3);// 三级菜单按钮初始化thirdLevelButtons = new ImageButton[3][3];// 第一组三级按钮 (从子按钮1展开)thirdLevelButtons[0][0] = floatingMenuView.findViewById(R.id.third_level_1_1);thirdLevelButtons[0][1] = floatingMenuView.findViewById(R.id.third_level_1_2);thirdLevelButtons[0][2] = floatingMenuView.findViewById(R.id.third_level_1_3);// 第二组三级按钮 (从子按钮2展开)thirdLevelButtons[1][0] = floatingMenuView.findViewById(R.id.third_level_2_1);thirdLevelButtons[1][1] = floatingMenuView.findViewById(R.id.third_level_2_2);thirdLevelButtons[1][2] = floatingMenuView.findViewById(R.id.third_level_2_3);// 第三组三级按钮 (从子按钮3展开)thirdLevelButtons[2][0] = floatingMenuView.findViewById(R.id.third_level_3_1);thirdLevelButtons[2][1] = floatingMenuView.findViewById(R.id.third_level_3_2);thirdLevelButtons[2][2] = floatingMenuView.findViewById(R.id.third_level_3_3);
然后就用button.setVisibility(View.GONE);把全部二级菜单全部隐藏起来
for循环也把三级菜单隐藏起来
// 初始隐藏所有二级和三级菜单for (ImageButton button : subButtons) {button.setVisibility(View.GONE);}for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {thirdLevelButtons[i][j].setVisibility(View.GONE);}}
所有的按钮都设置一下点击监听一下
// 设置主按钮点击事件mainButton.setOnClickListener(v -> toggleMainMenu());// 设置各个二级按钮的点击事件subButtons[0].setOnClickListener(v -> toggleThirdLevelMenu(0));subButtons[1].setOnClickListener(v -> toggleThirdLevelMenu(1));subButtons[2].setOnClickListener(v -> toggleThirdLevelMenu(2));// 设置三级按钮点击事件for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {final int groupIndex = i;final int buttonIndex = j;thirdLevelButtons[i][j].setOnClickListener(v -> {// 处理三级按钮点击Toast.makeText(this, "点击了按钮组" + (groupIndex + 1) + "的第" + (buttonIndex + 1) + "个按钮", Toast.LENGTH_SHORT).show();});}}
在设置一下滑动监听
setupDragListener(mainButton);
123
├── toggleMainMenu()
│ ├── 展开/收起二级菜单
│ └── 控制动画
│
├── toggleThirdLevelMenu()
│ ├── 展开/收起三级菜单
│ └── 控制动画
│
├── hideThirdLevelMenu()
│ └── 隐藏指定组的三级菜单
│
└── onDestroy()└── 移除悬浮窗视图
123
完整代码
FloatingMenuService.java
package com.example.testtest;import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageButton;
import android.widget.Toast;public class FloatingMenuService extends Service {private WindowManager windowManager;private View floatingMenuView;private WindowManager.LayoutParams params;private boolean isMenuExpanded = false;private ImageButton[] subButtons;private ImageButton[][] thirdLevelButtons;private boolean[] isThirdLevelExpanded = {false, false, false};private int initialX, initialY;private float initialTouchX, initialTouchY;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();// 初始化WindowManagerwindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);// 加载悬浮窗布局floatingMenuView = LayoutInflater.from(this).inflate(R.layout.floating_menu, null);// 设置窗口参数params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,WindowManager.LayoutParams.WRAP_CONTENT,getLayoutType(),WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,PixelFormat.TRANSLUCENT);params.gravity = Gravity.TOP | Gravity.START;params.x = 0;params.y = 100;// 添加视图到窗口windowManager.addView(floatingMenuView, params);// 初始化视图initializeViews();}private int getLayoutType() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {return WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;} else {return WindowManager.LayoutParams.TYPE_PHONE;}}private void initializeViews() {// 主菜单按钮ImageButton mainButton = floatingMenuView.findViewById(R.id.main_button);// 二级菜单按钮subButtons = new ImageButton[3];subButtons[0] = floatingMenuView.findViewById(R.id.sub_button_1);subButtons[1] = floatingMenuView.findViewById(R.id.sub_button_2);subButtons[2] = floatingMenuView.findViewById(R.id.sub_button_3);// 三级菜单按钮初始化thirdLevelButtons = new ImageButton[3][3];// 第一组三级按钮 (从子按钮1展开)thirdLevelButtons[0][0] = floatingMenuView.findViewById(R.id.third_level_1_1);thirdLevelButtons[0][1] = floatingMenuView.findViewById(R.id.third_level_1_2);thirdLevelButtons[0][2] = floatingMenuView.findViewById(R.id.third_level_1_3);// 第二组三级按钮 (从子按钮2展开)thirdLevelButtons[1][0] = floatingMenuView.findViewById(R.id.third_level_2_1);thirdLevelButtons[1][1] = floatingMenuView.findViewById(R.id.third_level_2_2);thirdLevelButtons[1][2] = floatingMenuView.findViewById(R.id.third_level_2_3);// 第三组三级按钮 (从子按钮3展开)thirdLevelButtons[2][0] = floatingMenuView.findViewById(R.id.third_level_3_1);thirdLevelButtons[2][1] = floatingMenuView.findViewById(R.id.third_level_3_2);thirdLevelButtons[2][2] = floatingMenuView.findViewById(R.id.third_level_3_3);// 初始隐藏所有二级和三级菜单for (ImageButton button : subButtons) {button.setVisibility(View.GONE);}for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {thirdLevelButtons[i][j].setVisibility(View.GONE);}}// 设置主按钮点击事件mainButton.setOnClickListener(v -> toggleMainMenu());// 设置各个二级按钮的点击事件subButtons[0].setOnClickListener(v -> toggleThirdLevelMenu(0));subButtons[1].setOnClickListener(v -> toggleThirdLevelMenu(1));subButtons[2].setOnClickListener(v -> toggleThirdLevelMenu(2));// 设置三级按钮点击事件for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {final int groupIndex = i;final int buttonIndex = j;thirdLevelButtons[i][j].setOnClickListener(v -> {// 处理三级按钮点击Toast.makeText(this, "点击了按钮组" + (groupIndex + 1) + "的第" + (buttonIndex + 1) + "个按钮", Toast.LENGTH_SHORT).show();});}}// 设置拖动事件setupDragListener(mainButton);}private void toggleMainMenu() {isMenuExpanded = !isMenuExpanded;// 如果要收起主菜单,也要收起所有三级菜单if (!isMenuExpanded) {// 收起所有三级菜单for (int i = 0; i < isThirdLevelExpanded.length; i++) {if (isThirdLevelExpanded[i]) {hideThirdLevelMenu(i, true);}}}if (isMenuExpanded) {// 展开菜单for (int i = 0; i < subButtons.length; i++) {ImageButton button = subButtons[i];button.setVisibility(View.VISIBLE);// 加载动画Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_in);animation.setStartOffset(i * 100); // 设置延迟,实现顺序展开效果button.startAnimation(animation);}} else {// 收起菜单for (int i = 0; i < subButtons.length; i++) {ImageButton button = subButtons[i];// 加载动画Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_out);animation.setStartOffset((subButtons.length - 1 - i) * 100); // 设置延迟,实现顺序收起效果button.startAnimation(animation);// 设置动画结束监听器final int index = i;animation.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {subButtons[index].setVisibility(View.GONE);}@Overridepublic void onAnimationRepeat(Animation animation) {}});button.startAnimation(animation);}}}private void toggleThirdLevelMenu(int groupIndex) {isThirdLevelExpanded[groupIndex] = !isThirdLevelExpanded[groupIndex];// 收起其他三级菜单for (int i = 0; i < isThirdLevelExpanded.length; i++) {if (i != groupIndex && isThirdLevelExpanded[i]) {hideThirdLevelMenu(i, false);isThirdLevelExpanded[i] = false;}}if (isThirdLevelExpanded[groupIndex]) {// 展开对应的三级菜单for (int i = 0; i < 3; i++) {ImageButton button = thirdLevelButtons[groupIndex][i];button.setVisibility(View.VISIBLE);// 加载动画Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_in);animation.setStartOffset(i * 70); // 设置较短的延迟,三级菜单展开稍快button.startAnimation(animation);}} else {// 收起对应的三级菜单hideThirdLevelMenu(groupIndex, false);}}private void hideThirdLevelMenu(int groupIndex, boolean immediately) {for (int i = 0; i < 3; i++) {ImageButton button = thirdLevelButtons[groupIndex][i];if (immediately) {button.clearAnimation();button.setVisibility(View.GONE);continue;}// 加载动画Animation animation = AnimationUtils.loadAnimation(this, R.anim.fade_out);animation.setStartOffset((3 - 1 - i) * 70); // 设置较短的延迟button.startAnimation(animation);// 设置动画结束监听器final int index = i;animation.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {}@Overridepublic void onAnimationEnd(Animation animation) {thirdLevelButtons[groupIndex][index].setVisibility(View.GONE);}@Overridepublic void onAnimationRepeat(Animation animation) {}});button.startAnimation(animation);}}private void setupDragListener(View view) {view.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:// 记录初始位置initialX = params.x;initialY = params.y;// 记录触摸点位置initialTouchX = event.getRawX();initialTouchY = event.getRawY();return true;case MotionEvent.ACTION_MOVE:// 计算移动距离params.x = initialX + (int) (event.getRawX() - initialTouchX);params.y = initialY + (int) (event.getRawY() - initialTouchY);// 更新窗口位置windowManager.updateViewLayout(floatingMenuView, params);return true;case MotionEvent.ACTION_UP:// 如果移动距离很小,则视为点击int xDiff = (int) (event.getRawX() - initialTouchX);int yDiff = (int) (event.getRawY() - initialTouchY);if (Math.abs(xDiff) < 5 && Math.abs(yDiff) < 5) {v.performClick();}return true;}return false;}});}@Overridepublic void onDestroy() {super.onDestroy();if (floatingMenuView != null && windowManager != null) {windowManager.removeView(floatingMenuView);}}
}
MainActivity.java
package com.example.testtest;import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private static final int OVERLAY_PERMISSION_CODE = 100;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button startButton = findViewById(R.id.start_floating_button);startButton.setOnClickListener(v -> checkOverlayPermission());}private void checkOverlayPermission() {// 检查是否已有悬浮窗权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {// 如果没有权限,请求权限Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,Uri.parse("package:" + getPackageName()));// Android 12之前使用startActivityForResultif (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {startActivityForResult(intent, OVERLAY_PERMISSION_CODE);} else {// Android 12及以上使用ActivityResultLaunchertry {overlayPermissionLauncher.launch(intent);} catch (Exception e) {Toast.makeText(this, "打开权限设置页面失败", Toast.LENGTH_SHORT).show();}}} else {// 已有权限,启动悬浮窗服务startFloatingMenuService();}}// 用于Android 12及以上版本的权限请求结果处理private final ActivityResultLauncher<Intent> overlayPermissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {// 检查用户是否授予了权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {startFloatingMenuService();} else {Toast.makeText(this, "需要悬浮窗权限才能使用此功能", Toast.LENGTH_SHORT).show();}});@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);// 处理Android 12之前的权限请求结果if (requestCode == OVERLAY_PERMISSION_CODE) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {startFloatingMenuService();} else {Toast.makeText(this, "需要悬浮窗权限才能使用此功能", Toast.LENGTH_SHORT).show();}}}private void startFloatingMenuService() {Intent serviceIntent = new Intent(MainActivity.this, FloatingMenuService.class);startService(serviceIntent);// 可选:启动服务后可以关闭Activity// finish();}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:padding="16dp"tools:context=".MainActivity"><TextViewandroid:id="@+id/title_text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerHorizontal="true"android:layout_marginTop="50dp"android:text="悬浮窗菜单示例"android:textSize="24sp"android:textStyle="bold" /><TextViewandroid:id="@+id/description_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/title_text"android:layout_marginTop="20dp"android:gravity="center"android:text="点击下方按钮启动带有菜单功能的悬浮窗"android:textSize="16sp" /><Buttonandroid:id="@+id/start_floating_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:padding="12dp"android:text="启动悬浮窗"android:textSize="18sp" /></RelativeLayout>
floating_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="16dp"><!-- 三级按钮组1 (显示在子按钮1下方) --><ImageButtonandroid:id="@+id/third_level_1_3"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_1"android:layout_marginTop="8dp"android:layout_alignEnd="@+id/sub_button_1"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_1_3"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_1" /><ImageButtonandroid:id="@+id/third_level_1_2"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_1"android:layout_marginTop="8dp"android:layout_marginEnd="8dp"android:layout_toStartOf="@+id/third_level_1_3"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_1_2"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_2" /><ImageButtonandroid:id="@+id/third_level_1_1"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_1"android:layout_marginTop="8dp"android:layout_marginEnd="8dp"android:layout_toStartOf="@+id/third_level_1_2"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_1_1"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_3" /><!-- 三级按钮组2 (显示在子按钮2下方) --><ImageButtonandroid:id="@+id/third_level_2_3"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_2"android:layout_marginTop="8dp"android:layout_alignEnd="@+id/sub_button_2"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_2_3"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_1" /><ImageButtonandroid:id="@+id/third_level_2_2"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_2"android:layout_marginTop="8dp"android:layout_marginEnd="8dp"android:layout_toStartOf="@+id/third_level_2_3"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_2_2"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_2" /><ImageButtonandroid:id="@+id/third_level_2_1"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_2"android:layout_marginTop="8dp"android:layout_marginEnd="8dp"android:layout_toStartOf="@+id/third_level_2_2"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_2_1"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_3" /><!-- 三级按钮组3 (显示在子按钮3下方) --><ImageButtonandroid:id="@+id/third_level_3_3"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_3"android:layout_marginTop="8dp"android:layout_alignEnd="@+id/sub_button_3"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_3_3"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_1" /><ImageButtonandroid:id="@+id/third_level_3_2"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_3"android:layout_marginTop="8dp"android:layout_marginEnd="8dp"android:layout_toStartOf="@+id/third_level_3_3"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_3_2"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_2" /><ImageButtonandroid:id="@+id/third_level_3_1"android:layout_width="40dp"android:layout_height="40dp"android:layout_below="@+id/sub_button_3"android:layout_marginTop="8dp"android:layout_marginEnd="8dp"android:layout_toStartOf="@+id/third_level_3_2"android:background="@drawable/circle_third_button_bg"android:contentDescription="@string/third_level_3_1"android:padding="8dp"android:visibility="gone"android:src="@drawable/ic_button_3" /><!-- 子按钮3 --><ImageButtonandroid:id="@+id/sub_button_3"android:layout_width="48dp"android:layout_height="48dp"android:layout_marginEnd="16dp"android:layout_toStartOf="@+id/sub_button_2"android:background="@drawable/circle_button_bg"android:contentDescription="@string/sub_button_3"android:padding="8dp"android:src="@drawable/ic_button_3" /><!-- 子按钮2 --><ImageButtonandroid:id="@+id/sub_button_2"android:layout_width="48dp"android:layout_height="48dp"android:layout_marginEnd="16dp"android:layout_toStartOf="@+id/sub_button_1"android:background="@drawable/circle_button_bg"android:contentDescription="@string/sub_button_2"android:padding="8dp"android:src="@drawable/ic_button_2" /><!-- 子按钮1 --><ImageButtonandroid:id="@+id/sub_button_1"android:layout_width="48dp"android:layout_height="48dp"android:layout_marginEnd="16dp"android:layout_toStartOf="@+id/main_button"android:background="@drawable/circle_button_bg"android:contentDescription="@string/sub_button_1"android:padding="8dp"android:src="@drawable/ic_button_1" /><!-- 主按钮 --><ImageButtonandroid:id="@+id/main_button"android:layout_width="56dp"android:layout_height="56dp"android:layout_alignParentEnd="true"android:background="@drawable/circle_main_button_bg"android:contentDescription="@string/main_button"android:padding="8dp"android:src="@drawable/ic_menu" /></RelativeLayout>
123
弹窗
1. 创建一个基本弹窗
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示").setMessage("确认删除吗?").setPositiveButton("确定", (dialog, which) -> {// 确定按钮点击逻辑Toast.makeText(this, "已删除", Toast.LENGTH_SHORT).show();}).setNegativeButton("取消", null).show();
2. 自定义布局弹窗
AlertDialog.Builder builder = new AlertDialog.Builder(this);// 加载自定义布局
View customView = LayoutInflater.from(this).inflate(R.layout.custom_dialog, null);
builder.setView(customView);AlertDialog dialog = builder.create();
dialog.show();// 绑定自定义布局中的组件
Button btnSubmit = customView.findViewById(R.id.btn_submit);
btnSubmit.setOnClickListener(v -> {dialog.dismiss(); // 关闭弹窗
});
容器
- 场景:当需要在一个弹窗中同时包含输入框、按钮、标题等多个组件时,容器可以将这些元素组织成一个整体。
- 优势:模块化的布局更易扩展和维护,例如后续新增一个按钮只需添加到容器中
// 创建容器并添加多个子视图
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);
container.setPadding(50, 30, 50, 10);TextView title = new TextView(context);
title.setText("用户信息");
container.addView(title);EditText input = new EditText(context);
container.addView(input);Button submitBtn = new Button(context);
submitBtn.setText("提交");
container.addView(submitBtn);
设置监听
// 创建容器并添加多个子视图
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);
container.setPadding(50, 30, 50, 10); // 注意:建议使用 dp 单位(见下文注意事项)TextView title = new TextView(context);
title.setText("用户信息");
container.addView(title);EditText input = new EditText(context);
container.addView(input);Button submitBtn = new Button(context);
submitBtn.setText("提交");// 设置点击监听
submitBtn.setOnClickListener(v -> {String text = input.getText().toString();if (!text.isEmpty()) {Toast.makeText(context, "提交内容:" + text, Toast.LENGTH_SHORT).show();} else {Toast.makeText(context, "请输入内容", Toast.LENGTH_SHORT).show();}
});container.addView(submitBtn);
改进后的MainActivity.java
package com.example.testtest;import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private static final int OVERLAY_PERMISSION_CODE = 100;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button startButton = findViewById(R.id.start_floating_button);startButton.setOnClickListener(v -> checkOverlayPermission());}private void checkOverlayPermission() {// 检查是否已有悬浮窗权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {// 如果没有权限,请求权限Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,Uri.parse("package:" + getPackageName()));// Android 12之前使用startActivityForResultif (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {startActivityForResult(intent, OVERLAY_PERMISSION_CODE);} else {// Android 12及以上使用ActivityResultLaunchertry {overlayPermissionLauncher.launch(intent);} catch (Exception e) {Toast.makeText(this, "打开权限设置页面失败", Toast.LENGTH_SHORT).show();}}} else {// 已有权限,启动悬浮窗服务startFloatingMenuService();}}// 用于Android 12及以上版本的权限请求结果处理private final ActivityResultLauncher<Intent> overlayPermissionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),result -> {// 检查用户是否授予了权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {startFloatingMenuService();} else {Toast.makeText(this, "需要悬浮窗权限才能使用此功能", Toast.LENGTH_SHORT).show();}});@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);// 处理Android 12之前的权限请求结果if (requestCode == OVERLAY_PERMISSION_CODE) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Settings.canDrawOverlays(this)) {startFloatingMenuService();} else {Toast.makeText(this, "需要悬浮窗权限才能使用此功能", Toast.LENGTH_SHORT).show();}}}private void startFloatingMenuService() {Intent serviceIntent = new Intent(MainActivity.this, FloatingMenuService.class);startService(serviceIntent);// 可选:启动服务后可以关闭Activity// finish();}
}
相关文章:
安卓基础(悬浮窗分级菜单和弹窗)
initializeViews() 初始化 把全部的按钮都弄出来 // 主菜单按钮ImageButton mainButton floatingMenuView.findViewById(R.id.main_button);// 二级菜单按钮subButtons new ImageButton[3];subButtons[0] floatingMenuView.findViewById(R.id.sub_button_1);subButtons[1]…...
HTTP基础介绍+OSI七层参考模型+HTTP协议介绍
图片来源于网络 图片来源于网络 浏览器 Chrome:谷歌浏览器,推荐 Safari(WebKit):苹果浏览器,iOS,macOS Firefox:火狐浏览器,开源插件特别多(FireBug) IE:Wi…...
【项目实践】boost 搜索引擎
1. 项目展示 boost搜索引擎具体讲解视频 2. 项目背景 对于boost库,官方是没有提供搜索功能的,我们这个项目就是来为它添加一个站内搜索的功能。 3. 项目环境与技术栈 • 项目环境: ubuntu22.04、vscode • 技术栈: C/C、C11、S…...
接口隔离原则(ISP)
非常好,**接口隔离原则(ISP: Interface Segregation Principle)是 SOLID 五大原则中的第四个,它专门解决“一个接口太臃肿”**导致的麻烦。 我来从以下几个维度详细拆解: 🧠 什么是接口隔离原则࿱…...
Leetcode刷题记录29——矩阵置零
题源:https://leetcode.cn/problems/set-matrix-zeroes/description/?envTypestudy-plan-v2&envIdtop-100-liked 题目描述: 思路一: 💡 解题思路 本题中我们采用如下策略: 第一次遍历整个矩阵,记…...
复刻低成本机械臂 SO-ARM100 组装篇(打螺丝喽)
视频讲解: 复刻低成本机械臂 SO-ARM100 组装篇(打螺丝喽) 组装的视频有很多,参考大佬的《手把手复刻HuggingFace开源神作之Follower机械臂组装,资料已整理》_哔哩哔哩_bilibili,跟着视频做,大体…...
[更新完毕]2025东三省B题深圳杯B题数学建模挑战赛数模思路代码文章教学:LED显示屏颜色转换设计与校正
完整内容请看文章最下面的推广群 已经更新完整的文章代码 基于非线性映射与深度模型的多通道LED显示屏色彩校正 摘要 本研究聚焦于高动态色彩空间下LED显示屏的色彩映射与逐点校正问题,结合非线性回归理论与深度学习模型,构建了一套涵盖BT.2020映射、RG…...
Easy云盘总结篇-登录注册
**说在前面:该项目是跟着B站一位大佬写的,不分享源码,支持项目付费 ** 获取图形验证码 可以看到这里有2两种图形验证码,分为: type0:如上图下面那个,是完成操作后要进行注册的验证码 type1: 如…...
04 基于 STM32 的时钟展示程序
前言 我们经常会看到 各个场合下面有 基于数码管 的时钟程序 比如 在车站, 教室, 办公室 等等 各个场合都有 然后 这里就是做一个 简单的 时钟程序 展示程序 测试用例 每一秒钟更新时间, 然后 迭代更新 天, 时, 分 等等 然后 主流程 基于 天, 时分秒 渲染数码管 #incl…...
音视频开发技术总结报告
音视频开发技术总结报告 一、音视频开发基础 1、音频基础 声音原理 声波特性:频率、振幅、波长人耳听觉范围:20Hz-20kHz声音三要素:音调、音量、音色 数字音频基础 采样率:常见44.1kHz、48kHz、96kHz量化位数:8bit、…...
FastAPI系列13:API的安全防护
API的安全防护 1、HTTPS 强制什么是HTTPS强制如何在FastAPI中实现HTTPS强制 2、CORS跨域资源共享什么是CORS在 FastAPI 中开启 CORS 3、SQL注入防护什么是SQL注入如何在FastAPI中实现SQL注入防护 4、CSRF防护什么是CSRF防护如何在FastAPI中实现CSRF防护 在 FastAPI系列12&…...
每天一道面试题@第五天
1.包装类型的缓存机制了解么? 指部分包装类在创建对象时,会将一定范围内的对象缓存起来,当再次使用相同值创建对象时,优先从缓存中获取,而不是重新创建新对象。【提高性能】【节省内存】 列举几个常见的包装类缓存机…...
Python硬核革命:从微控制器到FPGA的深度开发指南
1. 重新定义硬件开发:Python的颠覆性突破 传统硬件开发长期被C/C++和Verilog/VHDL统治,但Python正通过两条路径改变这一格局: 1.1 微控制器领域的MicroPython革命 完整Python 3.4语法支持,运行在资源受限的MCU上(最低要求:64KB ROM,16KB RAM) 直接内存访问能力,突破…...
WebRTC 服务器之Janus概述和环境搭建
1 概述 Janus 是由 Meetecho 开发的通用 WebRTC 服务器,它为构建 WebRTC 应用程序提供了一个模块化框架。服务器目标:Janus WebRTC 网关被设计为轻量级、通用的 WebRTC 服务器,除了实现以下方法外,它本身不提供任何功能࿱…...
mcp+llm+rag
MCPRAG简介 前言一、MCP是什么?二、MCP工作原理(1. MCP Hosts(主机)(2.MCP Clients(客户端)(3. MCP Servers(服务端)(4. Local Data Sources(本地数据源&…...
Seata RM的事务提交与回滚源码解析
文章目录 前言一、RM提交事务二、RM回滚事务2.1、undo校验逻辑2.2、执行回滚逻辑 总结RM 的事务提交与回滚行为说明(基于 Seata AT 模式)1. 提交阶段(Phase Two Commit)2. 回滚阶段(Phase Two Rollback) 前…...
Ubuntu 24.04 完整Docker安装指南:从零配置到实战命令大全
Ubuntu 24.04 完整Docker安装指南:从零配置到实战命令大全 文章目录 Ubuntu 24.04 完整Docker安装指南:从零配置到实战命令大全1. 安装 Docker2. 配置 Docker 镜像加速器2.1 配置 Docker 镜像源2.2 重启 Docker 服务 3. Docker 常用命令3.1 Docker 常用命…...
设计模式简述(十七)备忘录模式
备忘录模式 描述组件使用 描述 备忘录模式用于将对象的状态进行保存为备忘录,以便在需要时可以从备忘录会对象状态;其核心点在于备忘录对象及其管理者是独立于原有对象之外的。 常用于需要回退、撤销功能的场景。 组件 原有对象(包含自身…...
【ICMP协议深度解析】从网络诊断到安全实践
目录 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键报文类型说明协议版本对比 二、实战演示环境配置要求核心实验实现实验1:标准ping流程实验2:traceroute路径发现实验3:自定义ICMP…...
《应用开发突围指南:敏捷开发的实战精髓》
如何在应用开发中精准且深入地应用敏捷开发方法呢?让我们一同深入探索。 敏捷开发,绝非仅仅是一种开发流程,更是一种蕴含深刻智慧的理念与思维方式。它与传统开发模式有着本质的区别,传统开发模式如同严谨的线性旅程,…...
【Mytais系列】SqlSession
MyBatis 的 SqlSession 是框架的核心接口之一,它是应用程序与 MyBatis 交互的顶层 API,用于执行 SQL 命令、管理事务和访问数据库。以下是关于 SqlSession 的详细说明: 1. 核心功能 (1) 执行 SQL 操作 增删改查:通过方法如 sele…...
【掌握 DDL】:SQL 中的数据库与表管理
掌握 DDL:SQL 中的数据库与表管理 掌握 DDL:SQL 中的数据库与表管理数据库 DDL创建数据库查看数据库查看所有数据库查看数据库创建语句 进入数据库删除数据库备份数据库备份恢复 查看数据库连接深入理解数据库创建与删除数据库字符集与校验规则 表 DLL创…...
第43周:GAN总结
目录 摘要 Abstract 计算机视觉中的分类 架构变体 损失变体 时间序列中的GAN 连续型GAN 离散型GAN 总结 摘要 本周总结了GAN的变形,主要从图像处理和时间序列生成两部分入手,分别找出了其中比较经典的几种GAN变种模型,简单分析了…...
安卓基础(MediaProjection)
1. Display 类 作用:代表显示设备(手机屏幕、外接显示器)常用方法: display.getRotation() // 获取屏幕方向(横屏/竖屏) display.getRefreshRate() // 获取屏幕刷新率(如&…...
Android Compose 物联网(IoT)UI 组件库封装指南
Android Compose 物联网封装组件 在物联网(IoT)应用开发中,使用Jetpack Compose可以创建现代化、响应式的用户界面。以下是一些针对物联网场景的Compose封装组件思路和实现方法: 常用物联网组件封装 1. 设备状态指示器 Composable fun DeviceStatusI…...
实用在线工具箱OmniTools
简介 OmniTools 是一个自托管的网络应用,提供多种在线工具,旨在简化日常任务。它包含了一系列独立的、小型但实用的工具,涵盖了文件处理、文本操作、网络请求、系统监控等多个方面。 OmniTools 的设计理念是简单、易用、可定制,方…...
【AI大模型学习路线】第一阶段之大模型开发基础——第三章(大模型实操与API调用)单轮对话与多轮对话调用。
【AI大模型学习路线】第一阶段之大模型开发基础——第三章(大模型实操与API调用)单轮对话与多轮对话调用? 【AI大模型学习路线】第一阶段之大模型开发基础——第三章(大模型实操与API调用)单轮对话与多轮对话调用&…...
数字化转型进阶:26页华为数字化转型实践分享【附全文阅读】
本文分享了华为数字化转型的实践经验和体会。华为通过数字化变革,致力于在客户服务、供应链、产品管理等方面提高效率,并把数字世界带入每个组织,构建万物互联的智能世界。华为的数字化转型愿景是成为行业标杆,通过推进数字化战略、构建面向业务数字化转型的IT组织阵型、坚…...
Go语言的优势与应用场景 -《Go语言实战指南》
一、 Go语言的五大核心优势 1. 语法简洁,开发高效 Go语言借鉴了C语言的表达方式,但去掉了多余复杂的特性(如继承、多态、异常处理等),语法风格清晰明了,极大地降低了学习成本: • 无需头文件…...
3D人物关系图开发实战:Three.js实现自动旋转可视化图谱(附完整代码)
3D人物关系图开发实战:Three.js实现自动旋转可视化图谱 效果核心解析场景初始化自动旋转控制器节点创建(带图片和标签)关系连线动画循环数据格式说明 代码 效果 本文将带您使用Three.js实现一个带自动旋转功能的3D人物关系图谱,核…...
文件操作-
1. 为什么使⽤⽂件? 如果没有⽂件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运⾏程序,是看不到上次程序的数据的,如果要将数据进⾏持久化…...
硬件零基础入门(尚硅谷)
1 一个碳原子有一个自由电子。所以能够导电。 金刚石四个都是都弄成共价键了,所以没有自由电子不能自由电子。 2 新的电子进来,因为互斥电荷进行了定向运动,产生了能量。两边电子平衡就停止了。所以电池的负极有电子。 电荷就是质子和电…...
【Ai零件】高德开放平台MCP的API-key注册
前言 基本操作文档,为n8n等平台,调用高德MCP服务做准备,本文记录其API-Key的生成步骤。 操作步骤 高德开发平台官网:https://lbs.amap.com/ 完成后,进入控制台界面: 创建新应用 进入【应用管理】,点击页…...
安卓基础(startActivityForResult和onActivityResult)
onActivityResult 方法有三个参数: requestCode:启动 Activity 时传入的请求码,用于区分不同的启动请求。resultCode:返回结果的状态码,通常为 RESULT_OK 或 RESULT_CANCELED。data:一个 Intent 对象&…...
安卓基础(悬浮窗)
悬浮窗 import android.app.Service; import android.content.Context; import android.graphics.PixelFormat; import android.os.IBinder; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager…...
《windows GCC 版本升级到9以上》
《windows GCC 版本升级到9以上》 在 Windows 系统上升级 GCC 到 9 以上版本通常有两种主流方案:MinGW-w64 和 WSL(Windows Subsystem for Linux)。以下是具体操作步骤: 方案一:使用 MinGW-w64(原生 Windows 环境) 步骤 1:安装 MSYS2 MSYS2 是 Windows 上的软件分发…...
LeetCode —— 102. 二叉树的层序遍历
😶🌫️😶🌫️😶🌫️😶🌫️Take your time ! 😶🌫️😶🌫️😶🌫️😶🌫️…...
Python面向对象编程实战:从类定义到高级特性的进阶之旅(2/10)
摘要:本文介绍面向对象编程基础概念,包括类与对象、封装、继承和多态等。以Python语言为例,详细讲述了类的定义与使用、构造函数与析构函数、类的访问控制等。面向对象编程通过将数据和操作封装在一起,提高代码的模块化和可维护性…...
【AI论文】DeepCritic:使用大型语言模型进行有意识的批判
摘要:随着大型语言模型(LLMs)的快速发展,对其输出提供准确的反馈和可扩展的监督成为一个紧迫而关键的问题。 利用LLM作为评判模型来实现自动化监督是一种有前景的解决方案。 在这项工作中,我们专注于研究和提高LLM的数…...
硬件工程师面试常见问题(12)
第五十六问:PCI总线基本知识 关于PCI总线的描述,错误的是:(A)(4分) A.PCI总线是一个16位宽的总线。 B.PCI的地址线与数据线是复用的。 C.PCI是一种独立于处理器的总线标准,可以支持多种处理器。 D.PCI支持即插即用功能。 解释: …...
大数据Spark(五十八):Spark Pi介绍
文章目录 Spark Pi介绍 Spark Pi介绍 Spark Pi是Apache Spark官方提供的一个示例程序,该案例使用 Spark 进行分布式计算,通过蒙特卡罗方法估算圆周率(π)的值,其估算π原理如下: 上图中,正方形…...
深入理解 HttpExchange_Java 中构建 HTTP 服务的基础组件
1. 引言 1.1 Java 中的轻量级 HTTP 服务需求 随着微服务、工具类应用和嵌入式系统的兴起,开发者对轻量级 HTTP 服务的需求日益增长。相比引入庞大的框架(如 Spring Boot),使用 JDK 原生 API 构建 HTTP 服务成为一种快速、低依赖的替代方案。 JDK 提供了 com.sun.net.htt…...
MaC QT 槽函数和Lambda表达式
在C Qt框架中,槽函数(Slot)是一种特殊的成员函数,用于响应信号(Signal)的触发,从而实现对象间的通信和事件处理。 #include<QMessageBox>//包含槽函数的头文件 //定义槽函数 响应特定的信…...
JMM 与 JVM 运行时数据区有什么区别和联系?
JMM(Java Memory Model)和 JVM 运行时数据区(JVM Runtime Data Areas)是 Java 内存管理中的两个不同但密切相关的概念。 1. JVM 运行时数据区 (JVM Runtime Data Areas) 是什么? JVM 运行时数据区是 JVM 在程序执行过程…...
LeetCode Hot100题解
目录 一、数组 & 字符串 1. 两数之和(简单) 2. 删除有序数组中的重复项(简单) 3. 移除元素(简单) 4. 合并两个有序数组(简单) 5. 买卖股票的最佳时机(简单&…...
基于Jenkins的DevOps工程实践之Jenkins共享库
文章目录 前言Jenkins共享库结构1、共享库演示2、知识点补充3、实践使用共享库格式化输出日志4、groovy基础语法4.1、 什么是 Groovy?4.2、groovy特点4.3、运行方法4.4、标识符4.5、基本数据类型4.5.1、string类型4.5.2、list类型 4.6、函数使用4.7、正则表达式 5、…...
【安装指南】Docker 安装最新版 Nginx 并进行项目的编排
目录 一、Nginx 的介绍 1.1 开源版 Nginx ① 访问路由 ② 反向代理 ③ 负载均衡 ④ 内容缓存 ⑤ 可编程 1.2 商业版 Nginx Plus ① 负载均衡 ② 动态管理 ③ 安全控制 ④ 状态监控 ⑤ Kubernetes Ingress Controller ⑥ 流媒体 1.3 扩…...
MFC自定义控件开发与使用指南
MFC自定义控件开发与使用指南 1. 概述 MFC(Microsoft Foundation Classes)框架提供了丰富的内置控件,但在实际开发中,我们常常需要创建自定义控件来满足特定的界面需求。本文将详细介绍如何在MFC中开发自定义控件,并以CCustomTextControl为例,展示自定义控件的实现和使…...
Learning vtkjs之PolyDataNormals
法线可视化 介绍 polydata法线可视化 效果 核心代码 主要流程 const fullScreenRenderer vtkFullScreenRenderWindow.newInstance({background: [0, 0, 0],rootContainer: vtkContainerRef.current,});const renderer fullScreenRenderer.getRenderer();const renderWind…...
DeepSeek辅助学术写作之提交和出版以及评审过程分析提示词分享祝你顺利毕业~
目录 1.提交和出版 2.评审过程 大家好这里是AIWritePaper官方账号,官网👉AIWritePaper~ 宝子们可以使用小编精选的“ChatGPT研究论文提示词”集合来创建研究论文。利用DeepSeek的智能回应生成详尽有效的内容,这样可以加快研究论文的策划、创…...