android仿assistivetouch悬浮窗实现(带功能实现)

news2024/12/26 10:58:42

一、悬浮窗点击后的界面:

主要有四个功能,返回、应用程序、退出和主界面。其他功能也可以类似添加。

界面布局代码就不贴出来了,源码(切记需要签名才能让功能实现):下载地址

二、主要是检测系统启动或者app启动的时候出现悬浮窗,并且只有当检测到前台运行了特定程序才会隐藏图标。

<receiver android:name="com.bumblebee.remindeasy.StartupReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
        <service android:name="com.bumblebee.remindeasy.service.AuxiliaryService" />

添加检测BOOT_COMPLETED,需要添加权限,

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

三、开始写service,调用自定义的view。

public void onCreate() {
        super.onCreate();
        new EasyTouchView(this, this).initTouchViewEvent();
    }

四、自定义view的实现,主要是touchview的显示,以及移动操作和点击事件注册。

public EasyTouchView(Context context, ServiceListener listener) {
		super(context);
		mContext = context;
		mSerLisrener = listener;
		
		mHandler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);
				if (msg.what == 100) {
					if (isCellaretteAppforeground(mContext)) {//检测到“com.xxx.xxx“程序,将隐藏touchview
						mTouchView.setVisibility(View.INVISIBLE);
					} else {
						mTouchView.setVisibility(View.VISIBLE);//否则显示touchview
					}
					this.sendEmptyMessageDelayed(100, 500);
				}
			}
		};
	}
	/*
	 * 检测是否存在某个应用com.xxx.xxx在前台运行。
	 */
	public boolean isCellaretteAppforeground(Context context) {
		ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
		List<ActivityManager.RunningAppProcessInfo> appProcesses = am.getRunningAppProcesses();
		for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
			if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
				if ("com.xxx.xxx".equals(appProcess.processName)) {
					return true;
				}
				break;
			}
		}
		return false;
	}
	
	public void initTouchViewEvent() {
		initEasyTouchViewEvent();
		initSettingTableView();
	}

	private void initEasyTouchViewEvent() {
		// 设置载入view WindowManager参数,获取WindowManager
		mWManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
		midX = mWManager.getDefaultDisplay().getWidth() / 2 - 25;
		midY = mWManager.getDefaultDisplay().getHeight() / 2 - 44;
		mTouchView = LayoutInflater.from(mContext).inflate(R.layout.easy_touch_view, null);
		mIconImageView = (ImageView) mTouchView.findViewById(R.id.easy_touch_view_imageview);
		mTouchView.setBackgroundColor(Color.TRANSPARENT);

		mTouchView.setOnTouchListener(mTouchListener);
		WindowManager wm = mWManager;
		WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();
		mWMParams = wmParams;
		wmParams.type = 2003; // 这里的2002表示系统级窗口,你也可以试试2003。
		wmParams.flags = 40; // 设置桌面可控
		wmParams.width = 100;
		wmParams.height = 100;
		wmParams.format = -3; // 透明
		wm.addView(mTouchView, wmParams);
		
		mTouchView.setVisibility(View.INVISIBLE);
		mHandler.sendEmptyMessageDelayed(100, 1000);//发送消息
	}

	private void initSettingTableView() {
		mSettingTable = LayoutInflater.from(mContext).inflate(R.layout.floatmenu, null);//将悬浮窗布局载入,继而可以获取里面的控件进行点击事件注册

		btnback = (Button) mSettingTable.findViewById(R.id.btn_back);
		btnhome = (Button) mSettingTable.findViewById(R.id.btn_home_screen);
		btnexit = (Button) mSettingTable.findViewById(R.id.btn_exit);
		btnapps = (Button) mSettingTable.findViewById(R.id.btn_apps);

		btnback.setOnClickListener(mClickListener);
		btnhome.setOnClickListener(mClickListener);
		btnexit.setOnClickListener(mClickListener);
		btnapps.setOnClickListener(mClickListener);
	}
	private OnClickListener mClickListener = new OnClickListener() {
		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.btn_back: //执行了两次是因为第一次是回到小图标,然后在执行返回当前程序
				sendKeyCode(KeyEvent.KEYCODE_BACK);
				new Handler().postDelayed(new Runnable() {
					@Override
					public void run() {
						sendKeyCode(KeyEvent.KEYCODE_BACK);
					}
				}, 500);
				break;
			case R.id.btn_home_screen:
				sendKeyCode(KeyEvent.KEYCODE_HOME);
				break;
			case R.id.btn_exit:
				sendKeyCode(KeyEvent.KEYCODE_BACK);
				break;
			case R.id.btn_apps:
				sendKeyCode(KeyEvent.KEYCODE_APP_SWITCH);
				break;
			}
		}
	};
//借用 input keyevent keycode命令,不同功能改变keycode即可,keycode对应值就百度~
	private void sendKeyCode(final int keyCode) {
		new Thread() {
			public void run() {
				String keyCommand = "input keyevent " + keyCode;
				try {
					Process process = Runtime.getRuntime().exec(keyCommand);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}.start();
	}
//监听touch
	private OnTouchListener mTouchListener = new OnTouchListener() {
		float lastX, lastY;
		int paramX, paramY;

		public boolean onTouch(View v, MotionEvent event) {
			final int action = event.getAction();

			float x = event.getRawX();
			float y = event.getRawY();

			if (mTag == 0) {
				mOldOffsetX = mWMParams.x;
				mOldOffsetY = mWMParams.y;
			}

			switch (action) {
			case MotionEvent.ACTION_DOWN:
				motionActionDownEvent(x, y);
				break;

			case MotionEvent.ACTION_MOVE:
				motionActionMoveEvent(x, y);
				break;

			case MotionEvent.ACTION_UP:
				motionActionUpEvent(x, y);
				break;

			default:
				break;
			}

			return true;
		}

		private void motionActionDownEvent(float x, float y) {
			lastX = x;
			lastY = y;
			paramX = mWMParams.x;
			paramY = mWMParams.y;
		}

		private void motionActionMoveEvent(float x, float y) {
			int dx = (int) (x - lastX);
			int dy = (int) (y - lastY);
			mWMParams.x = paramX + dx;
			mWMParams.y = paramY + dy;
			mTag = 1;

			mWManager.updateViewLayout(mTouchView, mWMParams);
		}

		private void motionActionUpEvent(float x, float y) {
			int newOffsetX = mWMParams.x;
			int newOffsetY = mWMParams.y;
			if (mOldOffsetX == newOffsetX && mOldOffsetY == newOffsetY) {
				mPopuWin = new PopupWindow(mSettingTable, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
				mPopuWin.setTouchInterceptor(new OnTouchListener() {

					public boolean onTouch(View v, MotionEvent event) {
						if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
							hideSettingTable();
							return true;
						}
						return false;
					}
				});

				mPopuWin.setBackgroundDrawable(new BitmapDrawable());
				mPopuWin.setTouchable(true);
				mPopuWin.setFocusable(true);
				mPopuWin.setOutsideTouchable(true);
				mPopuWin.setContentView(mSettingTable);

				if (Math.abs(mOldOffsetX) > midX) {
					if (mOldOffsetX > 0) {
						mOldOffsetX = midX;
					} else {
						mOldOffsetX = -midX;
					}
				}

				if (Math.abs(mOldOffsetY) > midY) {
					if (mOldOffsetY > 0) {
						mOldOffsetY = midY;
					} else {
						mOldOffsetY = -midY;
					}
				}

				mPopuWin.setAnimationStyle(R.style.AnimationPreview);
				mPopuWin.setFocusable(true);
				mPopuWin.update();
				mPopuWin.showAtLocation(mTouchView, Gravity.CENTER, -mOldOffsetX, -mOldOffsetY);

				// TODO
				mIconImageView.setBackgroundDrawable(getResources().getDrawable(R.drawable.transparent));

				catchSettingTableDismiss();
			} else {
				mTag = 0;
			}
		}
	};

	private void catchSettingTableDismiss() {
		mTimer = new Timer();
		mTask = new TimerTask() {

			@Override
			public void run() {
				if (mPopuWin == null || !mPopuWin.isShowing()) {
					handler.sendEmptyMessage(0x0);
				}
			}
		};
		mTimer.schedule(mTask, 0, 100);
	}

	private void clearTimerThead() {
		if (mTask != null) {
			mTask.cancel();
			mTask = null;
		}

		if (mTimer != null) {
			mTimer.cancel();
			mTimer = null;
		}
	}

	Handler handler = new Handler() {
		public void handleMessage(Message msg) {
			mIconImageView.setBackgroundDrawable(getResources().getDrawable(R.drawable.touch_ic));
		};
	};

	public void showToast(Context context, String text) {
		if (mToast == null) {
			mToast = Toast.makeText(context, text, Toast.LENGTH_SHORT);
		} else {
			mToast.setText(text);
			mToast.setDuration(Toast.LENGTH_SHORT);
		}
		mToast.show();
	}

	private void hideSettingTable(String content) {
		hideSettingTable();
		showToast(mContext, content);
	}

	private void hideSettingTable() {
		if (null != mPopuWin) {
			mPopuWin.dismiss();
		}
	}

	public interface ServiceListener {
		public void OnCloseService(boolean isClose);
	}

切记:需要签名,才可以实现input keyevent keycode

源码下载:下载地址

www.rebootvip.com

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时序数据库荣登巅峰,被央视报道了!

8月30日&#xff0c;事务处理性能委员会TPC正式公布了最新的国际权威数据库性能基准榜单&#xff0c;“清华系”发起研制的Apache IoTDB开发的国产化时序数据库软件TimechoDB&#xff0c;在性能和系统成本维度上双双打破世界纪录。在央视《24小时》节目中&#xff0c;1分34秒重…

《黑神话:悟空》与游戏经济学的深度剖析

《黑神话&#xff1a;悟空》作为近年来备受瞩目的国产3A游戏大作&#xff0c;自其发布以来&#xff0c;不仅在游戏界内引起了轰动&#xff0c;更在多个消费领域产生了深远的影响。这款游戏不仅以其卓越的品质和深刻的文化内涵吸引了大量玩家的关注&#xff0c;还通过一系列连锁…

神策埋点 sensorsdata.es6.min.js、sensorsdata.min.js 触发eslint 语法检查,导致打包不成功

问题描述&#xff1a; 在使用神策埋点时&#xff0c;下载的web js sdk&#xff0c;打包时eslint 语法检查&#xff0c;会导致打包不成功。npm start没问题。 主要错误是&#xff1a; Line 1:204272: Expected an assignment or function call and instead saw an expression …

[Python]之深拷贝与浅拷贝

Python之深拷贝与浅拷贝 概述: ​ 大白话解释就是 深拷贝拷贝的多, 浅拷贝拷贝的少. 深浅拷贝区别就是: 拷贝的层级的多与少. 深浅拷贝都能操作可变类型 和 不可变类型, 但是深浅拷贝一般操作的都是 可变类型, 几乎不会出操作不可变类型的. 可变类型 和 不可变类型的划分依…

openssl RSA 密钥(key)、证书签名请求(csr)、证书(cer)的生成和例子

1. RSA 密钥(key)、证书签名请求&#xff08;csr&#xff09;、证书&#xff08;cer&#xff09;的生成顺序 2. 具体操作 a. 生成 RSA 密钥&#xff08;私钥&#xff09; openssl genrsa -aes256 -out ca.key 2048b. 生成证书签名请求(csr) # C-----国家&#xff08;Country…

Oracle超详细(数据库编程)

目录 一、数据类型 &#xff08;一&#xff09;数值型数据类型 &#xff08;二&#xff09;字符型数据类型 &#xff08;三&#xff09;日期和时间型数据类型 &#xff08;四&#xff09;大对象和二进制数据类型 &#xff08;五&#xff09;其他数据类型 &#xff08;六&…

离散余弦变换(Discrete Cosine Transform, DCT),信号去噪

介绍 离散余弦变换&#xff08;Discrete Cosine Transform, DCT&#xff09;是一种常用的信号处理工具&#xff0c;特别是在数据压缩、图像处理和模式识别等领域中。DCT的基本思想是将信号从空间域或时间域转换到频率域&#xff0c;以揭示信号中的频率成分。与离散傅里叶变换&…

C/C++网络编程--文件分块传输

文件分块传输是网络编程中一个常见的任务&#xff0c;尤其是在处理大文件时&#xff0c;将文件分块可以提高传输效率&#xff0c;简化错误处理&#xff0c;并可以实现并发传输。下面&#xff0c;写个从客户端向服务器发送大型数据的demo。 客户端 客户端有两点需要注意&#…

vue组件中的数据传递(2)--子组件传父组件

两种情况 子主动传 vue 父传子 子传父实现方式_vue父传子-CSDN博客 vue父子组件传值&#xff0c;父传子&#xff0c;子传父_父传子 且随时变化-CSDN博客 父主动要 Vue2.0的三种常用传值方式、父传子、子传父、非父子组件传值_父传子传-CSDN博客

File Transfer Server 文件传输服务器插件

您需要在本地不同设备之间传输文件吗?现在你可以做到了,你不必安装任何专用服务器。 文件传输服务器为您的游戏或应用程序添加了将文件从任何受支持的平台传输到任何受支持平台的能力。从移动到独立,从移动到移动等(查看支持的平台) 优势: -完整的源代码可用。 -不需要预…

【揭秘心梗元凶】不容忽视的七大生活习惯,竟是心梗“幕后推手”!

在这个快节奏的时代&#xff0c;心梗&#xff08;急性心肌梗死&#xff09;这一健康杀手正悄然逼近&#xff0c;威胁着越来越多人的生命安全。心梗不仅发病急骤&#xff0c;后果往往也极为严重。那么&#xff0c;心梗究竟是如何引起的&#xff1f;今天&#xff0c;我们就来揭开…

Leetcode面试经典150题-239.滑动窗口最大值

解法都在代码里&#xff0c;不懂就留言或者私信 官方定级hard难度&#xff0c;其实是medium&#xff0c;确实字节考过 class Solution {public int[] maxSlidingWindow(int[] nums, int k) {if(nums.length 1) {return new int[]{nums[0]};}/**我们要返回的是一个数组&#…

SoM的理解

对于终端客户来说&#xff0c;要思考到底怎么做一款产品。目前好像主流的就是SoC和SoM。以前联发科是有Turnkey项目&#xff0c;不过我记得我参与的项目&#xff0c;都是直接拿原厂的参考设计&#xff0c;基本上就是改一个壳&#xff0c;电路板&#xff0c;IO啥的都不动&#x…

土壤湿度传感器详解(STM32)

目录 一、介绍 二、传感器原理 1.原理图 2.引脚描述 三、程序设计 main.c文件 TS.h文件 TS.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 传感器适用于土壤的湿度检测&#xff0c;模块中蓝色的电位器是用于土壤湿度的阈值调节&#xff0c;数字量输出DO可以与…

进程间的通信(IPC)基础了解,匿名管道使用,有名管道使用

进程间通信基本知识 进程间通信的定义 进程间通信方式分类 匿名管道&#xff08;pipe&#xff09; 匿名管道介绍 创建方式&#xff1a;使用 pipe 系统调用创建&#xff0c;返回一对文件描述符&#xff08;读端和写端&#xff09;。生命周期&#xff1a;匿名管道的生命周期与…

为什么说RAG是AI 2.0时代的“杀手级”应用?

复旦AI博士&#xff0c;分享AI领域全维度知识与研究。应极客时间邀请开设《RAG快速开发实战》课程&#xff0c;感兴趣的同学可以访问关注 https://time.geekbang.com/column/intro/100804101 随着 AI 2.0 时代的来临&#xff0c;我们正站在一个技术革新和行业变革的交汇点。大语…

Vue3优化表单标签与布局,解决文字过长问题(附Demo)

目录 前言1. 增加标签宽度&#xff08;生效&#xff09;2. 工具提示 Tooltip&#xff08;勉勉强强&#xff09;3. 缩小字体&#xff08;不生效&#xff09;4. CSS 控制换行&#xff08;不推荐&#xff09; 前言 好不容易构思整个表单的布局&#xff0c;但是个别表单的文字过长…

springboot整合logback进行日志管理(上篇)

1、前言: 在日常开发中日志的打印与日志的记录是非常重要的。市面上主流的日志管理框架有log4j、logback,二者各有优缺点v,由于项目中比较常用的是logback(我们自己项目就是用的logback),进行就主要介绍一下logback在真是项目中是如何整合的。 2、springboot默认整合的logb…

NASA数据集:50 m分辨率的雪水当量(SWE)地图的集合

ASO L4 Lidar Snow Water Equivalent 50m UTM Grid V001 ASO L4 激光雷达雪水当量 50 米UTM 网格&#xff0c;第 1 版 简介 该数据集包含 50 米网格雪水当量 (SWE) 值&#xff0c;是 NASA/JPL 机载雪地观测站 (ASO) 飞机勘测活动的一部分。 这些数据来自 ASO L4 Lidar Snow …

OLED预警系统与按键菜单交互代码实操

引言 OLED顾名思义就是一个屏幕, 我们让一个屏幕在特定的时间, 显示特定的画面, 就是我们所需要的, 因为这里是涉及到环境预警,所以需要加入一个应急接管页面的选项, 所以我们要把按键直接操作画面, 变成按键操作完, 我们根据优先级判断之后, 才能确定要显示的是哪个画面. 比如…