显示悬浮窗口可以使用WindowManager的addView方法,为了关闭应用能够继续运行,打开应用的时候开启一个Service,让Service去设置和打开悬浮窗口。
WindowManager主要是使用3个方法:
addView方法用于添加一个悬浮窗,updateViewLayout方法用于更新悬浮窗的参数,removeView用于移除悬浮窗。
WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数,其中有几个经常会用到的变量:
type值用于确定悬浮窗的类型,一般设为2002,表示在所有应用程序之上,但在状态栏之下。
flags值用于确定悬浮窗的行为,比如说不可聚焦,非模态对话框等等,属性非常多,大家可以查看文档。
gravity值用于确定悬浮窗的对齐方式,一般设为左上角对齐,这样当拖动悬浮窗的时候方便计算坐标。
x值用于确定悬浮窗的位置,如果要横向移动悬浮窗,就需要改变这个值。
y值用于确定悬浮窗的位置,如果要纵向移动悬浮窗,就需要改变这个值。
width值用于指定悬浮窗的宽度。
height值用于指定悬浮窗的高度。
创建悬浮窗这种窗体需要向用户申请权限才可以的,因此还需要在AndroidManifest.xml中加入uses-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW”
代码
如图,这个蓝色的图标就是悬浮框了
打开Activity时启动FloatingWindowService
Intent intent = new Intent(MainActivity.this,FloatingWindowService.class);
startService(intent);
FloatingWindowService代码:
package com.flyelephant.notebook.service;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import com.flyelephant.notebook.R;
import com.flyelephant.notebook.constant.PreferenceConst;
import com.panda.commonlibrary.utils.Logout;
import com.panda.commonlibrary.utils.PreferencesUtils;
public class FloatingWindowService extends Service {
private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
private static WindowManager windowManager;
private static ImageView imageView;
public FloatingWindowService() {
}
@Override
public void onCreate() {
super.onCreate();
Logout.e("onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Logout.e("onStartCommand");
// 判断UI控件是否存在
if (imageView == null) {
// 1、获取系统级别的WindowManager
windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE);
// 2、使用Application context 创建UI控件,避免Activity销毁导致上下文出现问题
imageView = new ImageView(this);
imageView.setImageResource(R.mipmap.logo);
// 3、设置系统级别的悬浮窗的参数,保证悬浮窗悬在手机桌面上
// 系统级别需要指定type 属性
// TYPE_SYSTEM_ALERT 允许接收事件
// TYPE_SYSTEM_OVERLAY 悬浮在系统上
// 注意清单文件添加权限
//系统提示。它总是出现在应用程序窗口之上。
lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
| WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
// FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
// FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
lp.gravity = Gravity.LEFT | Gravity.TOP;
//显示位置与指定位置的相对位置差
lp.x = 0;
lp.y = 0;
//取出之前的位置
int fx = PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).getInt(PreferenceConst.FLOWINGWINDOW_X, 0);
int fy = PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).getInt(PreferenceConst.FLOWINGWINDOW_Y, 0);
Logout.e("fx:" + fx);
Logout.e("fy:" + fy);
//如果两个都是默认的数值,则认为是第一次使用
if (fx == 0 && fy == 0) {
//悬浮窗默认显示的位置
lp.gravity = Gravity.LEFT | Gravity.TOP;
//显示位置与指定位置的相对位置差
lp.x = 0;
lp.y = 0;
} else {
lp.x = fx;
lp.y = fy;
}
//悬浮窗的宽高
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.format = PixelFormat.TRANSPARENT;
windowManager.addView(imageView, lp);
//设置悬浮窗监听事件
imageView.setOnTouchListener(new View.OnTouchListener() {
private float lastX; //上一次位置的X.Y坐标
private float lastY;
private float nowX; //当前移动位置的X.Y坐标
private float nowY;
private float tranX; //悬浮窗移动位置的相对值
private float tranY;
@Override
public boolean onTouch(View v, MotionEvent event) {
boolean ret = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 获取按下时的X,Y坐标
lastX = event.getRawX();
lastY = event.getRawY();
ret = true;
break;
case MotionEvent.ACTION_MOVE:
// 获取移动时的X,Y坐标
nowX = event.getRawX();
nowY = event.getRawY();
// 计算XY坐标偏移量
tranX = nowX - lastX;
tranY = nowY - lastY;
// 移动悬浮窗
lp.x += tranX;
lp.y += tranY;
//更新悬浮窗位置
windowManager.updateViewLayout(imageView, lp);
//记录当前坐标作为下一次计算的上一次移动的位置坐标
lastX = nowX;
lastY = nowY;
break;
case MotionEvent.ACTION_UP:
//使用SharedPreferences记录当前的位置
PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).put(PreferenceConst.FLOWINGWINDOW_X, lp.x);
PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).put(PreferenceConst.FLOWINGWINDOW_Y, lp.y);
Logout.e("lp.x:" + lp.x);
Logout.e("lp.y:" + lp.y);
break;
}
return ret;
}
});
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
PreferencesUtils工具
package com.panda.commonlibrary.utils;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Map;
import java.util.Set;
/**
* 轻量级数据存储工具
*
*/
public class PreferencesUtils {
static PreferencesUtils singleton = null;
static SharedPreferences preferences;
static SharedPreferences.Editor editor;
private PreferencesUtils(Context context, String fileName) {
preferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
editor = preferences.edit();
}
public static PreferencesUtils getPreferences(Context context, String fileName) {
if (singleton == null) {
singleton = new Builder(context, fileName).build();
}
return singleton;
}
public void put(String key, String value) {
editor.putString(key, value);
editor.commit();
}
public void put(String key, boolean value) {
editor.putBoolean(key, value);
editor.commit();
}
public void put(String key, float value) {
editor.putFloat(key, value);
editor.commit();
}
public void put(String key, int value) {
editor.putInt(key, value);
editor.commit();
}
public void put(String key, Long value) {
editor.putLong(key, value);
editor.commit();
}
public void put(String key, Set<String> values) {
editor.putStringSet(key, values);
editor.commit();
}
public String getString(String key, String defValue) {
return preferences.getString(key, defValue);
}
public int getInt(String key, int defValue) {
return preferences.getInt(key, defValue);
}
public boolean getBoolean(String key, boolean defValue) {
return preferences.getBoolean(key, defValue);
}
public long getLong(String key, long defValue) {
return preferences.getLong(key, defValue);
}
public float getFloat(String key, float defValue) {
return preferences.getFloat(key, defValue);
}
public Set<String> getStringSet(String key, Set<String> defValues) {
return preferences.getStringSet(key, defValues);
}
public Map<String, ?> getAll() {
return preferences.getAll();
}
public void remove(String key) {
editor.remove(key).apply();
}
public void clear() {
editor.clear().apply();
}
private static class Builder {
private final Context context;
private final String fileName;
public Builder(Context context, String fileName) {
if (context == null)
throw new IllegalArgumentException("Context must not be null.");
this.context = context.getApplicationContext();
this.fileName = fileName;
}
/**
* Method that creates an instance of Prefs
*
* @return an instance of Prefs
*/
public PreferencesUtils build() {
return new PreferencesUtils(context, fileName);
}
}
}
常量,FILENAME是保存的xml文件名称
public class PreferenceConst {
public static String FILENAME="flyelephant";
public static String FLOWINGWINDOW_X="FlowingWindow_X";
public static String FLOWINGWINDOW_Y="FlowingWindow_Y";
}
Manifest文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.flyelephant.notebook">
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:name=".APPApplication"
android:allowBackup="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/logo"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".service.FloatingWindowService"
>
</service>
</application>
</manifest>
效果图:
参考