项目的开发时应用在登陆后显示一个悬浮窗,同时显示在线人数等一些其他信息,点击悬浮按钮可以显示全局弹窗名单。开发完成后,觉着需要记录一下这种实现方式。所以写一个简单的Demo。
Demo思路是通过启动Service来添加/移除 悬浮窗,因为是一个全局悬浮窗,所以选择依附于Service。
在MyAppliction中监听应用的前后台切换来添加或者隐藏悬浮窗。
其自定义的悬浮窗View在项目中是通过Canvas手动绘制的,这里图个省事直接加载一个布局文件。主要是想理解这种添加全局悬浮窗的方式,所以Demo样式和功能能简则简。
MyAppliction
package com.example.qxb_810.floatbuttondemo.application;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.WindowManager;
import com.example.qxb_810.floatbuttondemo.service.FloatingActionService;
import java.util.List;
import static android.content.ContentValues.TAG;
/**
* create 2018/12/1 13:31
* desc 自定义Application
*/
public class MyApplication extends Application {
private Intent mIntent;
private WindowManager.LayoutParams mFloatingLayoutParams = new WindowManager.LayoutParams();
public WindowManager.LayoutParams getmFloatingLayoutParams() {
return mFloatingLayoutParams;
}
@Override
public void onCreate() {
super.onCreate();
this.monitorActivityLifecycle();
}
/**
* 监听程序Activity声明周期
*/
private void monitorActivityLifecycle() {
this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
if (isRunningForeground()){
mIntent = new Intent(MyApplication.this, FloatingActionService.class);
startService(mIntent);
}
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
if (!isRunningForeground()){
stopService(mIntent);
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
/**
* 判断应用运行在前后台
*/
public boolean isRunningForeground() {
ActivityManager activityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
List appProcessInfos = activityManager.getRunningAppProcesses(); // 枚举进程
for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessInfos) {
if (appProcessInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
if (appProcessInfo.processName.equals(this.getApplicationInfo().processName)) {
Log.d(TAG, "EntryActivity isRunningForeGround");
return true;
}
}
}
Log.d(TAG, "EntryActivity isRunningBackGround");
return false;
}
}
FloatingActionService.java ---- 悬浮窗所依附的Service
package com.example.qxb_810.floatbuttondemo.service;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.example.qxb_810.floatbuttondemo.application.MyApplication;
import com.example.qxb_810.floatbuttondemo.button.FloatingActionButton;
/**
* create 2018/12/1 13:34
* desc 悬浮按钮Service
*/
public class FloatingActionService extends Service {
private WindowManager mWindowManager;
private FloatingActionButton mButton;
private int mScreenWidth;
private int mScreenHeight;
@Override
public void onCreate() {
super.onCreate();
this.initView();
this.initEvent();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
this.mWindowManager.removeViewImmediate(this.mButton);
}
/**
* 添加按钮
*/
private void initView() {
// 通过WindowManager来添加悬浮窗
this.mWindowManager = (WindowManager) this.getApplicationContext().getSystemService(WINDOW_SERVICE);
Display display = this.mWindowManager.getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
this.mScreenWidth = metrics.widthPixels;
this.mScreenHeight = metrics.heightPixels;
WindowManager.LayoutParams params = ((MyApplication) this.getApplication()).getmFloatingLayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mFloatingLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
mFloatingLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
}
params.format = PixelFormat.RGBA_8888;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.gravity = Gravity.LEFT | Gravity.TOP; // 左上为坐标点
// 设置View大小
params.height = 50;
params.width = 50;
// 设置View坐标位置
params.x = 0;
params.y = mScreenHeight / 2;
this.mButton = FloatingActionButton.getInstance(this);
this.mWindowManager.addView(mButton, params);
}
/**
* 初始化事件
*/
private void initEvent() {
this.mButton.setOnClickListener(v -> {
// 项目中是在这里进行添加 名单弹窗 和移除名单弹窗的操作,但名单弹窗的初始化不在这里
Toast.makeText(this, "点击了Button", Toast.LENGTH_SHORT).show();
});
}
}
FloatingActionButton – 弹窗按钮
package com.example.qxb_810.floatbuttondemo.button;
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.example.qxb_810.floatbuttondemo.R;
import com.example.qxb_810.floatbuttondemo.application.MyApplication;
import static android.content.Context.WINDOW_SERVICE;
/**
* create 2018/12/1 13:35
* desc 自定义悬浮按钮 -- 这里随便写的一个布局文件
*/
public class FloatingActionButton extends FrameLayout {
private Context mContext;
private float mStartPointX;
private float mStartPointY;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mFloatingLayoutParams;
private boolean isIntercept = false;
private int mStatusHeight;
public static FloatingActionButton getInstance(Context context) {
FloatingActionButton button = new FloatingActionButton(context);
return button;
}
public FloatingActionButton(Context context) {
this(context, null);
}
public FloatingActionButton(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public FloatingActionButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initView();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
/**
* 初始化View
*/
private void initView(){
// 随便添加一个简单的View布局作为悬浮窗
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_floating_button, null, false);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT);
this.addView(view, params);
this.mWindowManager = (WindowManager) getContext().getApplicationContext().getSystemService(WINDOW_SERVICE);
this.mFloatingLayoutParams = ((MyApplication) getContext().getApplicationContext()).getmFloatingLayoutParams();
this.mStatusHeight = getStatusHeight(mContext);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//获取相对屏幕的坐标,即以屏幕左上角为原点
float rawX = event.getRawX();
float rawY = event.getRawY() - mStatusHeight; //statusHeight是系统状态栏的高度
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
this.mStartPointX = event.getX();
this.mStartPointY = event.getY();
isIntercept = false;
break;
case MotionEvent.ACTION_MOVE:
mFloatingLayoutParams.x = (int)(rawX - mStartPointX);
mFloatingLayoutParams.y = (int)(rawY - mStartPointY);
this.mWindowManager.updateViewLayout(this, mFloatingLayoutParams);
isIntercept = true;
break;
case MotionEvent.ACTION_UP:
mFloatingLayoutParams.x = 0; // 这里的策略是默认松手吸附到左侧 如果需要吸附到右侧则改为mFloatingLayoutParams.x = mScreenWidth; mScreenWidth 是屏幕宽度,不想吸附则注释这一句即可
this.mWindowManager.updateViewLayout(this, mFloatingLayoutParams);
break;
}
return isIntercept ? isIntercept : super.onTouchEvent(event);
}
/**
* 状态栏的高度
*/
public static int getStatusHeight(Context context) {
int statusHeight = -1;
try {
Class clazz = Class.forName("com.android.internal.R$dimen"); //使用反射获取实例
Object object = clazz.newInstance();
int height = Integer.parseInt(clazz.getField("status_bar_height")
.get(object).toString());
statusHeight = context.getResources().getDimensionPixelSize(height);
} catch (Exception e) {
e.printStackTrace();
}
return statusHeight;
}
}
布局文件