android 应用内浮标(非WindowManager全局悬浮)

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

更多文章请关注公众号:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值