一、Android悬浮窗实现保活的缺点
在Android应用开发中,有时需要使用悬浮窗来实现一些特殊的需求,比如浮层弹窗、置顶通知等。但是悬浮窗会带来一些问题,保活就是其中之一。当我们启动悬浮窗时,系统需要给我们的应用分配系统资源,而此时如果经常操作该悬浮窗,那么系统可能就会kill掉你的应用,导致悬浮窗无法正常显示。当然,我们可以通过设置一些参数来缓解这个问题,比如设置悬浮窗类型、优化代码等。
以下是一个简单的示例代码,用于设置悬浮窗类型为TYPE_SYSTEM_ALERT:
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;//设置悬浮窗类型 layoutParams.format = PixelFormat.RGBA_8888; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; //创建悬浮窗 WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); View view = LayoutInflater.from(this).inflate(R.layout.layout_float_window, null); windowManager.addView(view, layoutParams);
二、Android开发悬浮窗
在Android中,悬浮窗的开发需要用到WindowManager类,同时需要注意悬浮窗的权限问题,可以在Manifest.xml中添加如下权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
为了保证悬浮窗正常显示,需要在代码中动态添加权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) { //开启悬浮窗设置页面 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); context.startActivity(intent); }
除了上述权限的设置,我们还需要在悬浮窗布局中添加一个关闭按钮,用于在关闭悬浮窗时释放资源。以下代码为一个示例:
//获取关闭按钮 Button btnCloseFloatWindow = (Button) viewFloatWindow.findViewById(R.id.btnCloseFloatWindow); //添加关闭按钮监听器 btnCloseFloatWindow.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { windowManager.removeView(viewFloatWindow);//移除悬浮窗 } });
三、Android悬浮窗实现灵动岛
灵动岛是一个比较流行的悬浮窗应用,它的主要功能是在屏幕上显示一个可供拖拽、伸缩和轮廓调整的悬浮窗,可以通过长按悬浮窗,弹出菜单执行不同的功能。以下是该应用的功能实现简述:
首先,我们需要定义悬浮窗布局,并添加可拖动、伸缩和轮廓调整的属性:
//获取悬浮窗布局 floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating, null); //添加可拖动、伸缩和轮廓调整属性 floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, PixelFormat.TRANSLUCENT);
接下来,我们需要监听悬浮窗拖拽事件:
//获取可拖动区域并添加触摸事件监听器 LinearLayout drag = floatingView.findViewById(R.id.dragView); drag.setOnTouchListener(new View.OnTouchListener() { private int x; private int y; private float screenX; private float screenY; private float viewX; private float viewY; private int touchState; @Override public boolean onTouch(View view, MotionEvent event) { boolean moved = false; switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: //按下事件 x = floatLayoutParams.x; y = floatLayoutParams.y; screenX = event.getRawX(); screenY = event.getRawY(); viewX = event.getX(); viewY = event.getY(); if (touchState == 0) { touchState = 1; } else if (touchState == 2) { touchState = 3; } break; case MotionEvent.ACTION_MOVE: //移动事件 float moveX = event.getRawX() - screenX; float moveY = event.getRawY() - screenY; if (touchState == 1 && Math.abs(moveX) < 5 && Math.abs(moveY) < 5) { break; } float moveViewX = event.getX() - viewX; float moveViewY = event.getY() - viewY; updateViewPosition((int) (x + moveX), (int) (y + moveY)); updateViewSize((int) moveViewX, (int) moveViewY); moved = true; if (touchState == 1) { touchState = 2; } else if (touchState == 3) { touchState = 4; } break; case MotionEvent.ACTION_UP: //抬起事件 case MotionEvent.ACTION_CANCEL: //取消事件 if (!moved && touchState == 1) { Intent intent = new Intent(FloatingService.this, LddWebViewActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } touchState = 0; break; } return true; } });
最后,我们需要添加弹出菜单的功能:
//获取菜单按钮并添加点击事件监听器 ImageView menu = floatingView.findViewById(R.id.menuView); menu.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { PopupMenu popupMenu = new PopupMenu(FloatingService.this, view); MenuInflater inflater = popupMenu.getMenuInflater(); inflater.inflate(R.menu.menu_ldd, popupMenu.getMenu()); popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.sections: Intent intent = new Intent(FloatingService.this, LddSectionsActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); break; case R.id.tools: Intent intent2 = new Intent(FloatingService.this, LddToolsActivity.class); intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent2); break; case R.id.exit: stopSelf(); break; } return true; } }); popupMenu.show(); } });
四、Android悬浮窗贴边
悬浮窗贴边可以提高应用使用的体验效果,以下代码实现了左、右、上、下四个方向的悬浮窗贴边:
//获取屏幕宽高 DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); int screenWidth = displayMetrics.widthPixels; int screenHeight = displayMetrics.heightPixels; //设置悬浮窗初始位置 floatLayoutParams.x = 0; floatLayoutParams.y = 0; windowManager.addView(floatingView, floatLayoutParams); //获取方向按钮并添加点击事件监听器 ImageView leftView = floatingView.findViewById(R.id.leftView); leftView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { floatLayoutParams.x = 0; windowManager.updateViewLayout(floatingView, floatLayoutParams); } }); ImageView topView = floatingView.findViewById(R.id.topView); topView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { floatLayoutParams.y = 0; windowManager.updateViewLayout(floatingView, floatLayoutParams); } }); ImageView rightView = floatingView.findViewById(R.id.rightView); rightView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { floatLayoutParams.x = screenWidth - floatingView.getWidth(); windowManager.updateViewLayout(floatingView, floatLayoutParams); } }); ImageView bottomView = floatingView.findViewById(R.id.bottomView); bottomView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { floatLayoutParams.y = screenHeight - floatingView.getHeight(); windowManager.updateViewLayout(floatingView, floatLayoutParams); } });
五、Android Studio悬浮窗
Android Studio是当前使用最广泛的Android应用开发工具,我们可以在它内部实现悬浮窗显示。以下是一个示例代码:
//获取主布局 View content = findViewById(android.R.id.content); //获取windowManager WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); //创建LayoutParams对象 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; //获取悬浮窗布局 View floatView = LayoutInflater.from(MainActivity.this).inflate(R.layout.layout_float_window, null); //添加悬浮窗插入到view中并设置位置 windowManager.addView(floatView, layoutParams);
六、Android悬浮窗源码
以下是Android悬浮窗源码:
public class FloatWindowService extends Service { private WindowManager.LayoutParams mParams; private WindowManager mWindowManager; private View mFloatView; @Override public void onCreate() { super.onCreate(); //创建一个浮动窗口实例 mFloatView = LayoutInflater.from(this).inflate(R.layout.float_layout, null); mParams = new WindowManager.LayoutParams(); mParams.type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE; mParams.format = PixelFormat.RGBA_8888; mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; mParams.gravity = Gravity.LEFT | Gravity.TOP; mParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mParams.height = WindowManager.LayoutParams.WRAP_CONTENT; //添加悬浮窗 mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); assert mWindowManager != null; mWindowManager.addView(mFloatView, mParams); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); if (mFloatView != null) { mWindowManager.removeView(mFloatView); } } }
七、Android应用内悬浮窗
应用内悬浮窗适用于一些特殊的应用场景,例如扫描应用、音乐应用等。以下是一个示例代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private SuspendButtonView suspendButtonView; private WindowManager windowManager; private WindowManager.LayoutParams layoutParams; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setUpView(); } private void setUpView() { suspendButtonView = new SuspendButtonView(this); suspendButtonView.setOnClickListener(this); suspendButtonView.setImageResource(R.drawable.ic_launcher); layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; layoutParams.format = PixelFormat.TRANSLUCENT; layoutParams.gravity = Gravity.LEFT | Gravity.TOP; layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); windowManager.addView(suspendButtonView, layoutParams); } @Override protected void onDestroy() { super.onDestroy(); if (suspendButtonView != null) { windowManager.removeView(suspendButtonView); } } @Override public void onClick(View view) { switch (view.getId()) { case R.id.suspend_button_view: break; } } }
以上代码定义了一个SuspendButtonView类,它继承自ImageView,并实现了添加到悬浮窗的功能。