android仿苹果悬浮窗(自动停靠、随手指滑动、返回主屏幕)

news/2024/11/18 4:26:39/

说明:本人写博客一来是为了方便日后查看项目,二来是希望能够和广大的程序猿相互交流学习,文章布局简单,如有嫌弃,请绕行,如有错误,请指出,谢谢。

实验环境:安卓6.0 魅族手机

悬浮窗主要有以下几个功能:

1、跟随手指的滑动而滑动(也可以用鼠标滑动)
2、在手指弹起的时候,悬浮窗会自动停靠在左右两侧
3、点击悬浮窗按钮可以返回到桌面

MainActivity中添加6.0访问权限

6.0权限问题:Google在6.0时加入权限管理机制,6.0之后,android需要动态获取权限,要使用权限,不仅要在manifest文件中定义,还要在代码中动态获取。点我了解权限问题

manifest中添加权限声明

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

MainActivity中代码如下:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (Settings.canDrawOverlays(MainActivity.this)) {Intent intent = new Intent(MainActivity.this, FloatViewService.class);Toast.makeText(MainActivity.this, "已开启悬浮窗", Toast.LENGTH_SHORT).show();startService(intent);finish();} else {//若没有权限,提示获取.Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);intent.setData(Uri.parse("package:" + getPackageName()));Toast.makeText(MainActivity.this, "需要取得权限以使用悬浮窗", Toast.LENGTH_SHORT).show();startActivity(intent);finish();}}
}

代码说明:如果手机已授予该app使用悬浮窗的功能,界面会自动开启悬浮窗,MainActivity被finish,否则直接跳转到本手机开启悬浮窗权限的界面,亲测有效,比如魅族手机开启权限的界面如下图所示:
亲测可以字节
问题:只有在第一次安装app的时候才会跳转到打开权限的界面,之后打开app则不会跳转,这部分不太理解,有知悉的评论区见。

悬浮窗界面的绘制

Android的窗口是基于WindowManager实现的,它面向的对象一端是屏幕,另一端就是View,比如我们之前使用的setContentView(R.layout.activity_main), 就是将view显示在屏幕上,代码的底层都是经过WindowManager实现的,整个系统只有一个WindowManager。点我了解界面绘制详解

Service实现后台运行

当app没有被关闭时,悬浮窗同样可以运行,这时候就需要Service来实现后台运行。这里可自行百度Service具体实现的过程,本篇不做解释。

跟随手指的滑动而滑动

说明:需要监听手势,所以设置了setOnTouchListener,识别按下、移动、弹起三个动作,移动的过程需要动态获取触摸的坐标,所以首先要在按下的过程中获取按下的坐标,rawX = event.getRawX(); rawY = event.getRawY(),在移动的过程中进行刷新, wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY。

        // 设置监听浮动窗口的触摸移动go_mainhome.setOnTouchListener(new View.OnTouchListener() {private float rawX;private float rawY;@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:
//                        Log.i("qqq", "onTouch------------------------------ACTION_DOWN: ");mFloatLayout.setAlpha(1.0f);//设置其透明度myCountDownTimer.cancel();//取消计时rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_MOVE:
//                        Log.i("qqq", "onTouch------------------------------ACTION_MOVE: ");// getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标int distanceX = (int) (event.getRawX() - rawX);int distanceY = (int) (event.getRawY() - rawY);//mFloatView.getMeasuredWidth()和mFloatView.getMeasuredHeight()都是100wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY;// 刷新mWindowManager.updateViewLayout(mFloatLayout, wmParams);rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_UP:myCountDownTimer.start();//重新开始计时if (wmParams.x < screenWidth / 2) {//在屏幕右侧wmParams.x = 0;wmParams.y = wmParams.y - 0;} else {//在屏幕左侧wmParams.x = screenWidth;wmParams.y = wmParams.y - 0;}mWindowManager.updateViewLayout(mFloatLayout, wmParams);break;}return false;//此处必须返回false,否则OnClickListener获取不到监听}});

获取屏幕大小
尝试了好几种获取屏幕大小的代码,此方法亲测有效。

        Display display = mWindowManager.getDefaultDisplay();Point point = new Point();display.getRealSize(point);screenWidth = point.x;screenHeight = point.y;Log.i("qqq", "screenWidth------: " + screenWidth + "\n" + "screenHeight---" + screenHeight);

停靠功能

说明:当手指滑动到屏幕中央右侧时,比如在图中的A点(x,y),最终悬浮窗将会停靠在图中的B点,A点向右平移到B点,纵坐标不变,横坐标为0,在屏幕左侧同理,可详见代码case MotionEvent.ACTION_UP部分,前提是需要设置 wmParams.gravity = Gravity.RIGHT | Gravity.BOTTOM,可以滑动最下面看详细代码。
在这里插入图片描述

点击悬浮窗按钮可以返回到桌面

其实就是对按钮设置了监听setOnClickListener,点击之后跳转到桌面的main。

Service中的代码

package com.lightingcontour.toucher;import android.annotation.SuppressLint;
import android.app.Service;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.Toast;public class FloatViewService extends Service {private static final String TAG = "FloatViewService";// 定义浮动窗口布局private LinearLayout mFloatLayout;private WindowManager.LayoutParams wmParams;// 创建浮动窗口设置布局参数的对象private WindowManager mWindowManager;private ImageButton go_mainhome;private ImageButton go_back;
//    private LinearLayout toucher_layout;private int screenHeight;private int screenWidth;private MyCountDownTimer myCountDownTimer;@Overridepublic void onCreate() {super.onCreate();Log.i(TAG, "onCreate");createFloatView();myCountDownTimer = new MyCountDownTimer(2500, 1000); //设置计时2.5smyCountDownTimer.start();}@SuppressWarnings("static-access")@SuppressLint("InflateParams")private void createFloatView() {wmParams = new WindowManager.LayoutParams();// 通过getApplication获取的是WindowManagerImpl.CompatModeWrappermWindowManager = (WindowManager) getApplication().getSystemService(getApplication().WINDOW_SERVICE);Display display = mWindowManager.getDefaultDisplay();Point point = new Point();display.getRealSize(point);screenWidth = point.x;screenHeight = point.y;Log.i("qqq", "screenWidth------: " + screenWidth + "\n" + "screenHeight---" + screenHeight);// 设置window typewmParams.type = WindowManager.LayoutParams.TYPE_PHONE;// 设置图片格式,效果为背景透明wmParams.format = PixelFormat.RGBA_8888;// 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;// 调整悬浮窗显示的停靠位置为右侧底部wmParams.gravity = Gravity.RIGHT | Gravity.BOTTOM;// 以屏幕左上角为原点,设置x、y初始值,相对于gravitywmParams.x = 0;wmParams.y = 0;// 设置悬浮窗口长宽数据wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;LayoutInflater inflater = LayoutInflater.from(getApplication());// 获取浮动窗口视图所在布局mFloatLayout = (LinearLayout) inflater.inflate(R.layout.toucherlayout, null);// 添加mFloatLayoutmWindowManager.addView(mFloatLayout, wmParams);// 浮动窗口按钮go_mainhome = (ImageButton) mFloatLayout.findViewById(R.id.go_mainhome);go_back = (ImageButton) mFloatLayout.findViewById(R.id.go_back);//UNSPECIFIED是未指定模式mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));// 设置监听浮动窗口的触摸移动go_mainhome.setOnTouchListener(new View.OnTouchListener() {private float rawX;private float rawY;@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:
//                        Log.i("qqq", "onTouch------------------------------ACTION_DOWN: ");mFloatLayout.setAlpha(1.0f);//设置其透明度myCountDownTimer.cancel();//取消计时rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_MOVE:
//                        Log.i("qqq", "onTouch------------------------------ACTION_MOVE: ");// getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标int distanceX = (int) (event.getRawX() - rawX);int distanceY = (int) (event.getRawY() - rawY);//mFloatView.getMeasuredWidth()和mFloatView.getMeasuredHeight()都是100wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY;// 刷新mWindowManager.updateViewLayout(mFloatLayout, wmParams);rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_UP:myCountDownTimer.start();//重新开始计时if (wmParams.x < screenWidth / 2) {//在屏幕右侧wmParams.x = 0;wmParams.y = wmParams.y - 0;} else {wmParams.x = screenWidth;wmParams.y = wmParams.y - 0;}mWindowManager.updateViewLayout(mFloatLayout, wmParams);break;}return false;//此处必须返回false,否则OnClickListener获取不到监听}});// 设置监听浮动窗口的触摸移动go_back.setOnTouchListener(new View.OnTouchListener() {private float rawX;private float rawY;@SuppressLint("ClickableViewAccessibility")@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:
//                        Log.i("qqq", "onTouch------------------------------ACTION_DOWN: ");mFloatLayout.setAlpha(1.0f);//设置其透明度myCountDownTimer.cancel();//取消计时rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_MOVE:
//                        Log.i("qqq", "onTouch------------------------------ACTION_MOVE: ");// getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标int distanceX = (int) (event.getRawX() - rawX);int distanceY = (int) (event.getRawY() - rawY);//mFloatView.getMeasuredWidth()和mFloatView.getMeasuredHeight()都是100wmParams.x = wmParams.x - distanceX;wmParams.y = wmParams.y - distanceY;// 刷新mWindowManager.updateViewLayout(mFloatLayout, wmParams);rawX = event.getRawX();rawY = event.getRawY();break;case MotionEvent.ACTION_UP:myCountDownTimer.start();//重新开始计时if (wmParams.x < screenWidth / 2) {//在屏幕右侧wmParams.x = 0;wmParams.y = wmParams.y - 0;} else {wmParams.x = screenWidth;wmParams.y = wmParams.y - 0;}mWindowManager.updateViewLayout(mFloatLayout, wmParams);break;}return false;//此处必须返回false,否则OnClickListener获取不到监听}});go_mainhome.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(FloatViewService.this, "返回到桌面",Toast.LENGTH_SHORT).show();Intent intent = new Intent();// 为Intent设置Action、Category属性intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setAction(Intent.ACTION_MAIN);// "android.intent.action.MAIN"intent.addCategory(Intent.CATEGORY_HOME); //"android.intent.category.HOME"CATEGORY_HOME  目标Activity是HOME Activity,即手机开机启动后显示的Activity,或按下HOME键后显示的ActivitystartActivity(intent);}});go_back.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(FloatViewService.this, "返回",Toast.LENGTH_SHORT).show();}});}@Overridepublic void onDestroy() {super.onDestroy();if (mFloatLayout != null) {// 移除悬浮窗口mWindowManager.removeView(mFloatLayout);}}@Overridepublic IBinder onBind(Intent intent) {return null;}public class MyCountDownTimer extends CountDownTimer {public MyCountDownTimer(long millisInFuture, long countDownInterval) {super(millisInFuture, countDownInterval);}@Overridepublic void onTick(long millisUntilFinished) {}@Overridepublic void onFinish() {mFloatLayout.setAlpha(0.4f);}}}

manifest中的代码

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.lightingcontour.toucher"><uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><service android:name=".FloatViewService" /></application></manifest>

布局中的代码

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:id="@+id/toucher_layout"android:orientation="vertical"android:layout_width="wrap_content"android:layout_height="wrap_content"><ImageButtonandroid:id="@+id/go_mainhome"android:layout_marginBottom="10dp"android:layout_width="50dp"android:layout_height="50dp"android:background="@drawable/go_mainhome" /><ImageButtonandroid:id="@+id/go_back"android:layout_width="50dp"android:layout_height="50dp"android:background="@drawable/go_back" /></LinearLayout></LinearLayout>

http://www.ppmy.cn/news/774557.html

相关文章

苹果悬浮球_买了一万块钱的苹果手机,悬浮球功能不会用?真的可惜了

苹果手机悬浮球就是大家俗称的"小白点"功能&#xff0c;虽然说现在是全面屏时代&#xff0c;大多数人都习惯了虚拟按键进行操作。也有很多快捷键功能。 但是iPhone手机小白点作为苹果手机经典功能之一&#xff0c;很多快捷功能非常实用&#xff0c;今天就来教教大家怎…

nginx七层代理和四层转发的理解

先来理解一下osi七层模型 应用层 应用层是ISO七层模型的最高层&#xff0c;它直接与用户和应用程序交互&#xff0c;提供用户与网络的接口。它包括各种应用协议&#xff0c;如HTTP、FTP、SMTP等&#xff0c;用于实现特定应用的功能和通信表示层 表示层…

hexo #01 安装

本篇主要步骤 1、前置环境2、安装 hexo3、初始化 hexo4、运行 hexo 1、前置环境 参考官网教程&#xff1a;hexo 博客系统安装前提 2、安装 hexo 这里采用官网推荐的第二种方式局部安装 hexo 包。 $ npm install hexo3、初始化 hexo 创建一个空文件夹&#xff0c;用来作为项…

进销存之“进”

进销存又称作是购销链。 进&#xff1a;指的是从询价——采购——入库&#xff0c;以及到最终的付款的过程&#xff1b; 销&#xff1a;指的是从报价——销售——出库&#xff0c;以及到最终的收款的过程&#xff1b; 存&#xff1a;指的是收料&#xff08;采购单收料、回收…

【酷柚易汛进销存开源版讲解】什么是核销单

核销单是用来处理预收业务、预付业务及其他一些无资金收支的往来款项抵消的业务单据。 1、业务类型&#xff1a;预收冲应收 指拿A客户预收的款抵消掉该向A客户收的款 。是收款单与销售出库单、销售退货单进行核销。 2、 业务类型&#xff1a;预付冲应付 指拿向A供应商预付的款…

进销存管理系统(一)---什么是进销存管理系统

这是本人在CSDN发表的第一遍博文&#xff0c;以前都没有写博文之类的东东&#xff0c;头一次还真不知道从何下手&#xff0c;先不管这些了&#xff0c;先写下吧以后发现不对的地方再改过来吧。 到现在为止从学校毕业到现在差不多正好一年了&#xff0c;做php开发有一年多的时间…

在进销存管理中经常提到红冲,那什么是红冲呢?

红冲的来历 红冲是红字冲账法的简称,又称红字更正法,是指用红字冲销或冲减原有的错误记录,以更正或调整记账错误的方法。红字冲账法按照其冲销错账的程序不同,可分为全额冲账法和差额冲账法两种。 全额冲账法是指将错账全部用红字冲销,再编制正确的记账凭证以更正错账的…

实验二 货物进销管理系统

1&#xff0e;掌握Java中文件的读写操作。 2&#xff0e;学会使用Java提供的实用类&#xff08;Vector, ArrayList&#xff09;来完成特定的功能。 3&#xff0e;掌握字符串类&#xff08;String, StringBuffer&#xff09;的使用。 4&#xff0e;掌握用面向对象的方法分析和解…