app在应用内的浮标我们第一个会想到WindowManager去添加一个view,但是WindowManager是有缺陷的比如现在系统高版本需要权限,并且不是app的而是全局的一个浮标,也就是说如果你的app退到后台如果你对这个浮标不做处理那么它还会一直显示。
我们先看看一般WindowManager怎么实现吧:
if (mWindowManager == null) {
mWindowManager = (WindowManager) context.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
}
wmParams = new WindowManager.LayoutParams();
//设置window type 下面变量2002是在屏幕区域显示,2003则可以显示在状态栏之上
//wmParams.type = LayoutParams.TYPE_PHONE;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
} else {
wmParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
}
// wmParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
//设置图片格式,效果为背景透明
wmParams.format = PixelFormat.RGBA_8888;
//设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
//wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
//设置可以显示在状态栏上
wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR |
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
wmParams.gravity = Gravity.LEFT | Gravity.TOP;
mWindowManager.addView(view, wmParams);
并且必须在AndroidManifest.xml申请TYPE_SYSTEM_ALERT和SYSTEM_OVERLAY_WINDOW权限并且在大于Build.VERSION_CODES.O之后的系统需要动态申请权限:
if (VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(getContext())) {
//有权限
} else {
//申请权限
Intent intent = new Intent("android.settings.action.MANAGE_OVERLAY_PERMISSION");
intent.setData(Uri.fromParts("package", aladdinWebView.getContext().getPackageName(), (String)null));
if (intent.resolveActivity(getPackageManager()) != null) {
((Activity)startActivityForResult(intent, 20368);
}
}
} else {
//小于23直接可以使用WindowManager
}
在Activity的onActivityResult判断如果用户给了权限继续执行:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 20368 && VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(GoModule.getContext())) {
Toast.makeText(GoModule.getContext(), "权限授予失败,无法开启悬浮窗", 0).show();
} else if (!TextUtils.isEmpty(this.params)) {
// 权限授予成功
}
}
}
}
总体来看使用WindowManager全局悬浮窗可以实现但是需要做一些处理并且需要动态申请权限,如果用户不给权限那么浮标不能显示,弊端还是挺明显的,那么我们能不能实现应用内的一个浮标并且不需要权限呢?
接下来我们来看一下怎么实现app内悬浮窗:
原理:我们监听app的生命周期在每一个activity的onActivityResumed的时候获取当前activity的rootview然后往上加一个view,onActivityPaused的时候将这个view从rootview移除(这里为什么没有在onCreate和onStop方法去添加和移除是因为当一个activity启动另外一个activity的时候可能新打开的activity的onResume已经走完了才执行第一个activity的onStop和onDestory方法,这样会导致判断逻辑有问题)。
实现:
首先全局监听app的生命周期registerActivityLifecycleCallbacks(new PActivityLifecycleCallbacks());
public class PActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
IModleFloatPlugin plugin=BaseModule.getModule(IModleFloatPlugin.TAG);
plugin.attach(activity);//添加悬浮view
}
@Override
public void onActivityPaused(Activity activity) {
IModleFloatPlugin plugin=BaseModule.getModule(IModleFloatPlugin.TAG);
plugin.detach(activity);//移除悬浮view
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
}
模块接口实现:
public class IModleFloatPluginImpl extends BaseModule implements IModleFloatPlugin {
@Override
public void attach(Activity activity) {
FloatingViewManager.get().attach(activity);
}
@Override
public void detach(Activity activity) {
FloatingViewManager.get().detach(activity);
}
}
接下来看一下悬浮窗管理类FloatingViewManager,主要是添加和删除悬浮view
public class FloatingViewManager implements IFloatingView {
private BaseFloatingView mFloatingView;//悬浮view
private static volatile FloatingViewManager mInstance;
private WeakReference<FrameLayout> mContainer;//rootview
@LayoutRes
private int mLayoutId = R.layout.float_layout;//悬浮view的布局
@DrawableRes
private int mIconRes = R.drawable.ic_launcher;//悬浮icon
private ViewGroup.LayoutParams mLayoutParams = getParams();
private Handler handler=new Handler(Looper.getMainLooper());
private FloatingViewManager() {
}
public static FloatingViewManager get() {
if (mInstance == null) {
synchronized (FloatingViewManager.class) {
if (mInstance == null) {
mInstance = new FloatingViewManager();
}
}
}
return mInstance;
}
/**
* 移除悬浮view
* @return
*/
@Override
public FloatingViewManager remove() {
handler.post(new Runnable() {
@Override
public void run() {
if (mFloatingView == null) {
return;
}
if (ViewCompat.isAttachedToWindow(mFloatingView) && getContainer() != null) {
getContainer().removeView(mFloatingView);
}
mFloatingView = null;
}
});
return this;
}
/**
* 确保悬浮view
*/
private void ensureFloatingView() {
synchronized (this) {
if (mFloatingView != null) {
return;
}
PFloatingView pFloatingView = new PFloatingView(getContainer().getContext(), mLayoutId);
mFloatingView = pFloatingView;
pFloatingView.setLayoutParams(mLayoutParams);
pFloatingView.setIconImage(mIconRes);
addViewToWindow(pFloatingView);
}
}
@Override
public FloatingViewManager add() {
ensureFloatingView();
return this;
}
/**
* 显示悬浮view
* @param activity
* @return
*/
@Override
public FloatingViewManager attach(Activity activity) {
attach(getRootView(activity));
ensureFloatingView();
return this;
}
@Override
public FloatingViewManager attach(FrameLayout container) {
if (container == null || mFloatingView == null) {
mContainer = new WeakReference<>(container);
return this;
}
if (mFloatingView.getParent() == container) {
return this;
}
if (getContainer() != null && mFloatingView.getParent() == getContainer()) {
getContainer().removeView(mFloatingView);
}
mContainer = new WeakReference<>(container);
container.addView(mFloatingView);
return this;
}
@Override
public FloatingViewManager detach(Activity activity) {
detach(getRootView(activity));
return this;
}
/**
* 移除view
* @param container
* @return
*/
@Override
public FloatingViewManager detach(FrameLayout container) {
if (mFloatingView != null && container != null && ViewCompat.isAttachedToWindow(mFloatingView)) {
container.removeView(mFloatingView);
}
if (getContainer() == container) {
mContainer = null;
}
return this;
}
@Override
public BaseFloatingView getView() {
return mFloatingView;
}
@Override
public FloatingViewManager icon(@DrawableRes int resId) {
mIconRes = resId;
return this;
}
@Override
public FloatingViewManager customView(BaseFloatingView viewGroup) {
mFloatingView = viewGroup;
return this;
}
@Override
public FloatingViewManager customView(@LayoutRes int resource) {
mLayoutId = resource;
return this;
}
@Override
public FloatingViewManager layoutParams(ViewGroup.LayoutParams params) {
mLayoutParams = params;
if (mFloatingView != null) {
mFloatingView.setLayoutParams(params);
}
return this;
}
/**
* 将view添加到rootview
* @param view
*/
private void addViewToWindow(final View view) {
if (getContainer() == null) {
return;
}
getContainer().addView(view);
}
private FrameLayout getContainer() {
if (mContainer == null) {
return null;
}
return mContainer.get();
}
private FrameLayout.LayoutParams getParams() {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM | Gravity.START;
params.setMargins(13, params.topMargin, params.rightMargin, 500);
return params;
}
/**
* 获取activity的rootview
* @param activity
* @return
*/
private FrameLayout getRootView(Activity activity) {
if (activity == null) {
return null;
}
try {
return (FrameLayout) activity.getWindow().getDecorView().findViewById(android.R.id.content);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
自定义悬浮view:
/**
* 自定义悬浮view
*/
public class BaseFloatingView extends FrameLayout {
public static final int MARGIN_EDGE = 20;//边距
private float mOriginalRawX;//上次view X坐标
private float mOriginalRawY;//上次view Y坐标
private float mOriginalX;//当前view X坐标
private float mOriginalY;//当前view Y坐标
protected MoveAnimator mMoveAnimator;
protected int mScreenWidth;//屏幕狂赌
private int mScreenHeight;//屏幕高度
private int mStatusBarHeight;//状态栏高度
private boolean isNearestLeft = false;//
public BaseFloatingView(Context context) {
this(context, null);
}
public BaseFloatingView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BaseFloatingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mMoveAnimator = new MoveAnimator();
mStatusBarHeight = FloatingUtils.getStatusBarHeight(getContext());
setClickable(true);
updateSize();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event == null) {
return false;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
changeOriginalTouchParams(event);
updateSize();
mMoveAnimator.stop();
break;
case MotionEvent.ACTION_MOVE:
updateViewPosition(event);
break;
case MotionEvent.ACTION_UP:
moveToEdge();
break;
}
return true;
}
private void updateViewPosition(MotionEvent event) {
setX(mOriginalX + event.getRawX() - mOriginalRawX);
// 限制不可超出屏幕高度
float desY = mOriginalY + event.getRawY() - mOriginalRawY;
if (desY < mStatusBarHeight) {
desY = mStatusBarHeight;
}
if (desY > mScreenHeight - getHeight()) {
desY = mScreenHeight - getHeight();
}
setY(desY);
}
private void changeOriginalTouchParams(MotionEvent event) {
mOriginalX = getX();
mOriginalY = getY();
mOriginalRawX = event.getRawX();
mOriginalRawY = event.getRawY();
}
protected void updateSize() {
mScreenWidth = (FloatingUtils.getScreenWidth(getContext()) - this.getWidth());
mScreenHeight = FloatingUtils.getScreenHeight(getContext());
}
public void moveToEdge() {
moveToEdge(isNearestLeft());
}
public void moveToEdge(boolean isLeft) {
float moveDistance = isLeft ? MARGIN_EDGE : mScreenWidth - MARGIN_EDGE;
mMoveAnimator.start(moveDistance, getY());
}
public void setNearestLeft(boolean isLeft){
this.isNearestLeft=isLeft;
}
protected boolean isNearestLeft() {
int middle = mScreenWidth / 2;
isNearestLeft = getX() < middle;
return isNearestLeft;
}
protected class MoveAnimator implements Runnable {
private Handler handler = new Handler(Looper.getMainLooper());
private float destinationX;
private float destinationY;
private long startingTime;
void start(float x, float y) {
this.destinationX = x;
this.destinationY = y;
startingTime = System.currentTimeMillis();
handler.post(this);
}
@Override
public void run() {
if (getRootView() == null || getRootView().getParent() == null) {
return;
}
float progress = Math.min(1, (System.currentTimeMillis() - startingTime) / 400f);
float deltaX = (destinationX - getX()) * progress;
float deltaY = (destinationY - getY()) * progress;
move(deltaX, deltaY);
if (progress < 1) {
handler.post(this);
}
}
private void stop() {
handler.removeCallbacks(this);
}
}
private void move(float deltaX, float deltaY) {
setX(getX() + deltaX);
setY(getY() + deltaY);
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateSize();
moveToEdge(isNearestLeft);
}
要显示的悬浮view:
public class PFloatingView extends BaseFloatingView {
private final ImageView mIcon;
private int defResId;
public PFloatingView(@NonNull Context context) {
this(context, R.layout.float_layout);
}
public PFloatingView(@NonNull Context context, @LayoutRes int resource) {
super(context, null);
inflate(context, resource, this);
mIcon = findViewById(R.id.icon);
}
public void setDefIconResId(int defResId) {
this.defResId = defResId;
}
public void setIconImage(@DrawableRes int resId){
mIcon.setImageResource(resId);
}
public void setIconImage(String imageUrl){
Glide.with(getContext().getApplicationContext())
.load(imageUrl)
.apply(new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(defResId)
.error(defResId))
.into(mIcon);
}
}
通过FloatingViewManager就能实现对悬浮窗的添加和移除。
demo:https://github.com/xxnan/ModlePlugin
更多文章请关注公众号: