在Android 添加悬浮窗需要两个了解两个知识点
1. Window
Window是一个抽象类,PhoneWindow是它的唯一实现类。
Window实际上是View的直接管理者。
Android中的所有视图都是通过Window来实现的。
不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的。
View是Android中呈现视图的方式,但是View不能单独存在,必须附着在Window这个抽象的概念上。
有视图的地方就有Window。
2. WindowManager
创建一个Window需要通过WindowManager。
Window的具体实现在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
Window 参数
Window设置属性都是通过一个LayoutParams来实现的,这个WindowManager.LayoutParams是WindowManager的静态内部类,用来管理Window的参数。
1. Flags参数
Flags参数可以控制Window的显示特性。Flags参数非常多,详细看这里。
常用的有下面几种:
FLAG_NOT_TOUCH_MODAL
设置后Window区域外的事件传递给下层的Window,区域内的事件自己处理。
一般都会设置该参数,否则其它Window会收不到事件。
FLAG_NOT_FOCUSABLE
不接受任何输入事件,跳不出软键盘。
这个参数会同时也会开启FLAG_NOT_TOUCH_MODAL。
2. Type参数
这个参数表示Window的类型,分为三种:
应用Window:对应着一个Activity。
子Window:不能单独存在,附属在父Window中,比如Dialog。
系统Window:需要声明权限,比如Toast、系统状态栏。
Window是分层的,每个Window都有对应的z-ordered,大层级的Window会覆盖在小层级的上面。层级就对应着Type参数。
下篇文章将讲解下如何通过Type参数 自定义层级关系
言归正传
Window的添加是通过addView()来实现的。
WindowManagerImpl # addView()
在源码中,可以看出WindowManagerImpl并没有直接实现,而是全部交给mGlobal。
下面的代码为创造一个 可以拖动的悬浮窗口
package com.example.floatwindow;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import java.lang.reflect.Method;
public class FloatWindow extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "FloatWindow";
private static final int REQUEST_CODE = 1;
WindowManager mWindowManager;
WindowManager.LayoutParams mwParams;
LinearLayout mFloatLayout;
Button float_btn;
Button start_btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
start_btn = findViewById(R.id.start_btn);
start_btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.start_btn:
if (floatPermissionCheck(this)) {
createFloatView();
}else {
requestFLoatPermission();
}
}
}
private void requestFLoatPermission() {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
Log.i(TAG, "onActivityResult granted");
createFloatView();
} else {
Log.i(TAG, "onActivityResult not Granted");
}
}
}
}
@SuppressLint("ClickableViewAccessibility")
private void createFloatView() {
mwParams = new WindowManager.LayoutParams();
mWindowManager = this.getWindowManager();
mwParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
mwParams.format = PixelFormat.RGBA_8888;
mwParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mwParams.gravity = Gravity.LEFT | Gravity.TOP;
mwParams.x = 0;
mwParams.y = 100;
mwParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mwParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mwParams.width = 200;
mwParams.height = 200;
LayoutInflater inflater = this.getLayoutInflater();
mFloatLayout = (LinearLayout) inflater.inflate(R.layout.layout_item, null);
float_btn = mFloatLayout.findViewById(R.id.float_btn);
mWindowManager.addView(mFloatLayout,mwParams);
float_btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
mwParams.x = (int) event.getRawX() - mFloatLayout.getWidth() / 2;
mwParams.y = (int) event.getRawY() - mFloatLayout.getHeight()/ 2;
mWindowManager.updateViewLayout(mFloatLayout,mwParams);
return false;
}
});
float_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FloatWindow.this, "我可以移动", Toast.LENGTH_SHORT).show();
}
});
}
// @Override
// protected void onDestroy() {
// super.onDestroy();
// if (mFloatLayout != null) {
// mWindowManager.removeView(mFloatLayout);
// }
// }
//判断权限
private boolean floatPermissionCheck(Context context) {
Boolean result = true;
if (Build.VERSION.SDK_INT >= 23) {
try {
Class clazz = Settings.class;
Method canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context.class);
result = (Boolean) canDrawOverlays.invoke(null, context);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
return result;
}
}