目录
- 前言
- 一、添加对悬浮窗功能的支持
- 二、通过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;@Overridepublic 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;@Overrideprotected 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);}@Overridepublic 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();}}@Overrideprotected 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@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic 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);}@Overridepublic 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 Typeif (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;@Overridepublic 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() {@Overridepublic 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);}}};@Overridepublic 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效果。