目录
- 前言
- 一、添加对悬浮窗功能的支持
- 二、通过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效果。