先来看一下实现效果吧
功能较为简单,直接贴出主要实现代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener{
private WindowManager.LayoutParams mParams;
private WindowManager mWindowManager;
private Button mFloatingButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}
private void init(){
mWindowManager=(WindowManager) getSystemService(Context.WINDOW_SERVICE);
findViewById(R.id.add).setOnClickListener(this);
findViewById(R.id.remove).setOnClickListener(this);
}
private void requestWindowPermission(){
//android 6.0或者之后的版本需要发一个intent让用户授权
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
if(!Settings.canDrawOverlays(getApplicationContext())){
Intent intent=new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:"+getPackageName()));
startActivityForResult(intent,100);
}
}
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.add){
//设置允许弹出悬浮窗口的权限
requestWindowPermission();
//创建窗口布局参数
mParams=new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,0,0,PixelFormat.TRANSPARENT);
//设置悬浮窗坐标
mParams.x=100;
mParams.y=100;
//表示该Window无需获取焦点,也不需要接收输入事件
mParams.flags=WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
mParams.gravity = Gravity.LEFT | Gravity.TOP;
Log.d("MainActivity","sdk:"+Build.VERSION.SDK_INT);
//设置window 类型
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){//API Level 26
mParams.type=WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mParams.type=WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
}
//创建悬浮窗(其实就创建了一个Button,这里也可以创建其他类型的控件)
if(null == mFloatingButton){
mFloatingButton=new Button(this);
mFloatingButton.setText("Floating");
mFloatingButton.setOnTouchListener(this);
mFloatingButton.setOnClickListener(this);
mWindowManager.addView(mFloatingButton,mParams);
}
} else if(v.getId()==R.id.remove){
if(null != mFloatingButton){
mWindowManager.removeView(mFloatingButton);
mFloatingButton=null;
}
} else if(v==mFloatingButton){
Toast.makeText(getApplicationContext(),"Click",Toast.LENGTH_SHORT).show();
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX=(int)event.getRawX();
int rawY=(int)event.getRawY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mParams.x=rawX;
mParams.y=rawY;
mWindowManager.updateViewLayout(mFloatingButton,mParams);
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return false;
}
@Override
protected void onDestroy() {
super.onDestroy();
if(null != mFloatingButton){
mWindowManager.removeView(mFloatingButton);
}
}
}
相关说明:(源码也已上传,有兴趣的可以获取源码运行一下)
1. 不要忘记在 manifest 文件中添加以下权限
因为在android 6.0 以后添加悬浮窗需要在manifest中添加权限:
Checks if the specified context can draw on top of other apps. As of API level 23, an app cannot draw on top of other apps unless it declares the Manifest.permission.SYSTEM_ALERT_WINDOW
permission in its manifest, and the user specifically grants the app this capability. To prompt the user to grant this approval, the app must send an intent with the action ACTION_MANAGE_OVERLAY_PERMISSION
, which causes the system to display a permission management screen.
详情可以参考 google 文档:
https://developer.android.google.cn/reference/android/provider/Settings?hl=en#canDrawOverlays(android.content.Context)
2. 关于 WindowManager.LayoutParams 中 type参数的设置
由于在 Android 8.0 或者之后的版本 以下字段被废弃
TYPE_SYSTEM_OVERLAY
TYPE_SYSTEM_ERROR
TYPE_PHONE
所以在设置 type 字段时需要进行版本的判断
详细信息也可以参考 Google 官方文档
public static final int TYPE_PHONE
This constant was deprecated in API level 26.
for non-system apps. Use TYPE_APPLICATION_OVERLAY
instead.
Google 文档地址:
https://developer.android.google.cn/reference/android/view/WindowManager.LayoutParams?hl=en#TYPE_PHONE
资源下载地址:
https://download.csdn.net/download/lollo01/12099115