安卓悬浮窗----可移动的悬浮窗

目录

  • 前言
  • 一、添加对悬浮窗功能的支持
  • 二、通过service实现悬浮窗
    • 2.1 窗口属性和标志
    • 2.2 窗口移动
  • 三、完整代码


前言

记录一下基础的悬浮窗实现,分为几个重要的点进行阐述。

一、添加对悬浮窗功能的支持

app要实现悬浮窗功能,首先app要添加对悬浮窗功能的支持。

manifest文件添加权限:

   <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

app内也要去进行界面跳转,在设置里打开该应用的悬浮窗权限支持。

  if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivity(intent);
                }

在这里插入图片描述

二、通过service实现悬浮窗

通过Button开启服务的方式来实现悬浮窗,使用Windowmanager添加悬浮窗View。

2.1 窗口属性和标志

悬浮窗的属性一般为TYPE_APPLICATION_OVERLAY、TYPE_SYSTEM_ALERT、TYPE_TOAST、TYPE_APPLICATION_OVERLAY 等,这里采用TYPE_APPLICATION_OVERLAY 赋予悬浮窗基本属性。

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
// 设置悬浮框不可触摸
 params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

2.2 窗口移动

对view进行ontouch事件的重写,更新坐标即可

  // 设置悬浮框的Touch监听
  btnView.setOnTouchListener(new View.OnTouchListener() {
      //保存悬浮框最后位置的变量
      int lastX, lastY;
      int paramX, paramY;

      @Override
      public boolean onTouch(View v, MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  lastX = (int) event.getRawX();
                  lastY = (int) event.getRawY();
                  paramX = params.x;
                  paramY = params.y;
                  break;
              case MotionEvent.ACTION_MOVE:
                  int dx = (int) event.getRawX() - lastX;
                  int dy = (int) event.getRawY() - lastY;
                  params.x = paramX + dx;
                  params.y = paramY + dy;
                  // 更新悬浮窗位置
                  windowManager.updateViewLayout(btnView, params);
                  break;
          }
          return true;
      }
  });

三、完整代码

确实挺简单的,没什么可讲的。

activity

public class WindowActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn_on;
    Button btn_off;
    Boolean isOpen = false;
    Intent mIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_window);
        bindViews();
    }

    private void bindViews() {
        btn_on = findViewById(R.id.btn_on);
        btn_on.setOnClickListener(this);
        btn_off = findViewById(R.id.btn_off);
        btn_off.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_on:
                mIntent = new Intent(WindowActivity.this, FloatService.class);
                mIntent.putExtra(FloatService.OPERATION, FloatService.OPERATION_SHOW);
                if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivity(intent);
                } else {
                    startService(mIntent);
                    Toast.makeText(WindowActivity.this, "悬浮框已开启~", Toast.LENGTH_SHORT).show();
                    isOpen = true;
                }
                break;
            case R.id.btn_off:
                if (isOpen) {
                    stopService(mIntent);
                    isOpen = false;
                }
                Toast.makeText(WindowActivity.this, "悬浮框已关闭~", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isOpen) {
            stopService(mIntent);
            isOpen = false;
        }
    }
}

FloatService

public class FloatService extends Service {
    Button btnView;
    WindowManager windowManager;
    WindowManager.LayoutParams params;
    Boolean isAdded;
    public static String OPERATION = "是否需要开启";
    public static int OPERATION_SHOW = 1;
    public static int OPERATION_HIDE = 2;
    int HANDLE_CHECK_ACTIVITY = 0;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int operation = intent.getIntExtra(OPERATION, 3);
        if (operation == OPERATION_SHOW) {
            mHandler.sendEmptyMessage(HANDLE_CHECK_ACTIVITY);
        } else if (operation == OPERATION_HIDE) {
            mHandler.removeMessages(HANDLE_CHECK_ACTIVITY);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        createWindowView();
        super.onCreate();
    }

    @SuppressLint("ClickableViewAccessibility")
    private void createWindowView() {
        btnView = new Button(getApplicationContext());
        btnView.setBackgroundResource(R.drawable.author);
        windowManager = (WindowManager) getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE);
        params = new WindowManager.LayoutParams();

        // 设置悬浮框不可触摸
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
        params.format = PixelFormat.RGBA_8888;
        // 设置悬浮框的宽高
        params.width = 200;
        params.height = 200;
        params.gravity = Gravity.LEFT;
        params.x = 200;
        params.y = 000;
        // 设置Window Type
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        // 设置悬浮框的Touch监听
        btnView.setOnTouchListener(new View.OnTouchListener() {
            //保存悬浮框最后位置的变量
            int lastX, lastY;
            int paramX, paramY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();
                        paramX = params.x;
                        paramY = params.y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int dx = (int) event.getRawX() - lastX;
                        int dy = (int) event.getRawY() - lastY;
                        params.x = paramX + dx;
                        params.y = paramY + dy;
                        // 更新悬浮窗位置
                        windowManager.updateViewLayout(btnView, params);
                        break;
                }
                return true;
            }
        });
        windowManager.addView(btnView, params);
        isAdded = true;
    }

    /**
     * 判断当前界面是否是桌面
     * android 6.0以上只能判断当前应用包名和Launcher
     */
    private boolean isAtHome() {
        ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> runningTaskInfos = mActivityManager.getRunningTasks(1);
        Log.d("henry", "是否在主页面" + runningTaskInfos);
        return getHomeApplicationList().contains(runningTaskInfos.get(0).topActivity.getPackageName());
    }

    /**
     * 获得属于桌面的应用的应用包名称
     *
     * @return 返回包含所有包名的字符串列表
     */

    /**
     * 获得属于桌面的应用的应用包名称
     * 返回包含所有包名的字符串列表数组
     *
     * @return
     */
    private List<String> getHomeApplicationList() {
        List<String> names = new ArrayList<String>();
        PackageManager packageManager = this.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resolveInfos) {
            names.add(resolveInfo.activityInfo.packageName);
        }
        Log.d("henry", "主屏幕应用列表" + names);
        return names;
    }

    //定义一个更新界面的Handler
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == HANDLE_CHECK_ACTIVITY) {
//                if (isAtHome()) {
                if (!isAdded) {
                    windowManager.addView(btnView, params);
                    isAdded = true;
                    new Thread(new Runnable() {
                        public void run() {
                            for (int i = 0; i < 10; i++) {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                Message m = new Message();
                                m.what = 2;
                                mHandler.sendMessage(m);
                            }
                        }
                    }).start();
                }
//                } else {
//                    if (isAdded) {
//                        windowManager.removeView(btnView);
//                        isAdded = false;
//                    }
//                }
                mHandler.sendEmptyMessageDelayed(HANDLE_CHECK_ACTIVITY, 100);
            }
        }
    };

    @Override
    public void onDestroy() {
        if (isAdded) {
            windowManager.removeView(btnView);
        }
        mHandler.removeCallbacksAndMessages(null);
        windowManager = null;
        mHandler = null;
        super.onDestroy();
    }
}

别忘了在Manifest文件声明service

        <service android:name="com.henry.windowManagerTest.My_Floating_Window.FloatService" />

看一下实现效果:

在这里插入图片描述

后续增加缩放+MPAndroidChart效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/625285.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【瑞萨RA6M3】2. UART 实验

https://blog.csdn.net/qq_35181236/article/details/132789258 使用 uart9 配置 打印 void hal_entry(void) {/* TODO: add your own code here */fsp_err_t err;uint8_t c;/* 配置串口 */err g_uart9.p_api->open(g_uart9.p_ctrl, g_uart9.p_cfg);while (1){g_uart9.…

扫码枪与Input的火花

文章目录 前言一、需求&#xff1a;交互细节二、具体实现两个核心的函数&#xff1a;自动聚焦 三&#xff0c;扩展知识input 与 change的区别 前言 在浏览器扫描条形码获取条形的值&#xff0c;再操作对应的逻辑。这是比较常见的业务&#xff0c;这里记录实际操作。 其中PC端…

spacy NER 位置信息不考虑空格!!!

texts ["疫情期间&#xff0c;俄罗斯 联邦军队医疗机构的负责人Saanvi Alia在方城县启动了远程医疗服务。","疫情期间&#xff0c;俄罗斯 联 邦 军队医疗机构的负责人Saanvi Alia在方城县启动了远程医疗服务。","疫情期间&#xff0c;俄罗 斯 联 邦 …

PR对比模板|手机竖屏分辨率视频效果前后对比模板剪辑素材

Premiere Pro前后对比效果模板&#xff0c;适用于化妆前后对比、视频调色效果前后对比、同一地方人物活场景变化等视频制作剪辑使用。 主要特点&#xff1a; 只需将图像或视频导入占位符&#xff0c;编辑前后文本&#xff0c;并使用控件微调动画计时。 可以打开或关闭前后屏幕…

LeetCode2095删除链表的中间节点

题目描述 给你一个链表的头节点 head 。删除 链表的 中间节点 &#xff0c;并返回修改后的链表的头节点 head 。长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点&#xff08;下标从 0 开始&#xff09;&#xff0c;其中 ⌊x⌋ 表示小于或等于 x 的最大整数。对于 n 1、…

Linux防火墙iptalbes

1 iptalbes 1.1 概念 防火墙(Firewall)是一种隔离技术&#xff0c;用于安全管理与筛选的软件和硬件设备&#xff0c;使计算机内网和外网分开&#xff0c;可以防止外部网络用户以非法手段通过外部网络进入内部网络&#xff0c;保护内网免受外部非法用户的侵入。 1.2 SELinux …

Linux文件相关

权限&#xff1a; 超级用户root 可以做任何事情不受限制 普通用户[用户名]做有限的事情 超级用户的命令提示符是“#”&#xff0c;普通用户的命令提示符是“$” 拓展&#xff1a; 用户的切换 su [用户名] 只是简单的换了一个账号&#xff0c;环境没变 su - 改变…

实验十 智能手机互联网程序设计(微信程序方向)实验报告

实验目的和要求 完成以下页面设计。 二、实验步骤与结果&#xff08;给出对应的代码或运行结果截图&#xff09; Wxml <view class"container"> <view class"header"> <view class"logo"…

遇到难题 暗区突围掉宝Twitch绑定关联账号显示404

Twitch作为一个广受欢迎的直播平台&#xff0c;经常会举办各种与游戏相关的互动活动&#xff0c;如“掉宝活动”&#xff0c;其中就包括了与《暗区突围》的合作。这类活动允许观众在观看指定的Twitch直播时&#xff0c;通过将他们的Twitch账号与《暗区突围》游戏账号绑定&#…

2024年3月 电子学会青少年等级考试机器人理论真题六级

202403 青少年等级考试机器人理论真题六级 第 1 题 下列选项中&#xff0c;属于URL的是&#xff1f;&#xff08; &#xff09; A&#xff1a;192.168.1.10 B&#xff1a;www.baidu.com C&#xff1a;http://www.kpcb.org.cn/h-col-147.html D&#xff1a;fe80::7998:ffc8…

springMVC基础使用(示例)

maven依赖&#xff08;javax.servlet-api版本与spring-webmvc班恩要匹配不然会报java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRespons&#xff09;&#xff1a; <dependencies><dependency><groupId>javax.servlet</groupId><arti…

贪心 -力扣860.柠檬水找零力扣2208.将数组和减半的最少操作次数力扣179.最大数力扣376.摆动序列

目录 力扣860.柠檬水找零 力扣2208.将数组和减半的最少操作次数 力扣179.最大数 力扣376.摆动序列 贪心策略&#xff0c;局部最优->全局最优 1.把解决问题的过程分为若干步骤 2.解决每一步的时候&#xff0c;都选择当前看起来“最优秀的”解法 3.希望能够得到全局最优解…

硬盘架构原理及其算法RAID工作原理写惩罚

一、硬盘的架构以及寻址原理 硬盘工作原理&#xff1a; 硬盘寻址原理&#xff1a;逻辑顺序磁道、盘片、扇区&#xff08;顺序CHS&#xff09; 二、机械硬盘算法 读取算法 寻道算法 个人与企业适合的算法和寻道 个人使用的机械硬盘适合的寻道算法和读取算法是&#xff1a…

Matlab如何批量导出多张高质量论文插图?科研效率UpUp第9期

上一期文章中&#xff0c;分享了Matlab导出高质量论文插图的方法&#xff08;Matlab如何导出高质量论文插图&#xff1f;科研效率UpUp第8期&#xff09;。 进一步&#xff0c;假如我们想要批量导出多张高质量无变形论文插图&#xff0c;该如何操作呢&#xff1f; ​也很简单&…

工作太闲怎么办?有没有什么副业推荐?

如果您的工作太闲&#xff0c;可以考虑参加一些副业&#xff0c;利用您的空余时间进行一些有意义的活动。以下是一些副业建议 1. 在线兼职 可以通过一些在线平台寻找兼职工作&#xff0c;如做在线调查、参与评估、进行数据输入等。 2.做任务 还可以做下百度的致米宝库&#…

vue获取路由的值

1&#xff0c;此方法获取到请求地址后面的值 如 /name123&age12 2&#xff0c;此方法获取到请地址&#xff1f;后面的值 例如?name123&age12 二者的区别&#xff0c;第一个是直接在路径后面拼接&#xff0c;第二种就是正规的http请求。 路径带&#xff1f;号的

比大小(打擂台)(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//声明比较大小函数max; int max(int a, int b);int main() {//初始化变量值&#xff1b;int i, n, m, a[10];//填充数组&#xff1b;printf("请输入10个数…

【瑞萨RA6M3】1. 基于 vscode 搭建开发环境(后续)

编译 mkdir build cd build cmake .. -G"Unix Makefiles" make -j或者 cmake -Bbuild -G"Unix Makefiles" cmake --build build创建快捷指令&#xff1a; 删除 .vscode/tasks.json&#xff0c; 存储占用和生成 MAP 编译完成后&#xff0c;打印内存占用…

ETF基金交易费用最低万0.5!开通账户注意事项!

场内基金ETF交易佣金默认是和股票一样万分之三的&#xff0c;投资者可以联系客户经理协商降低etf交易佣金&#xff0c;目前市场上证券公司最低的etf交易佣金是万分之0.5。 ​ETF基金的佣金计算方式是&#xff1a;佣金成交金额手续费率。 投资者在交易ETF基金时&#xff0c;除…

mysql主从热备部署

1、主从复制原理 mysql之间数据复制的基础是二进制日志文件。一台mysql数据库一旦开启用日志文件后&#xff0c;其作为master&#xff0c;它的数据库所有操作都会以事件的方式记录在二进制日志中&#xff0c;其他数据库作为slave通过一个I/O线程与主数据库保持通信&#xff0c;…