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

更多文章请关注公众号:

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用 WindowManager 类来实现全局悬浮窗。首先需要获取 WindowManager 对象,然后创建一个 LayoutParams 对象,设置悬浮窗的宽高、位置等属性,最后将悬浮窗的 View 添加到 WindowManager 中即可。需要注意的是,需要在 AndroidManifest.xml 文件中添加 SYSTEM_ALERT_WINDOW 权限才能显示全局悬浮窗。 ### 回答2: 实现全局悬浮窗在Android系统开发中是一个相对复杂的部分。下面是一种实现方法: 首先,需要在AndroidManifest.xml文件中申请悬浮窗权限。在<manifest>标签下加入以下权限声明: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 然后,创建一个Service类用于管理悬浮窗的显示和隐藏。在Service类中,可以通过WindowManager来实现悬浮窗的控制。在onCreate()方法中,可以创建一个悬浮窗视图,并设置其布局和参数。在启动该Service时,调用WindowManager的addView()方法将悬浮窗添加到界面上。 在悬浮窗视图上,可以添加所需的UI组件和逻辑。通过设置触摸事件监听,可以实现拖拽悬浮窗的功能。同时监听点击事件,可以实现悬浮窗的点击响应。 为了实现全局悬浮窗,需要注意在Service中设置悬浮窗的类型为TYPE_SYSTEM_ALERT或TYPE_APPLICATION_OVERLAY。这样可以让悬浮窗在其他应用程序之上显示。 此外,为了提高悬浮窗的交互性,可以通过设置悬浮窗的动画效果,实现悬浮窗的平滑移动和渐变效果。 最后,在完成悬浮窗的开发后,不要忘记在Service的onDestroy()方法中移除悬浮窗,避免资源泄漏。 综上所述,实现全局悬浮窗的方法包括:在Manifest文件中申请权限、创建Service类实现悬浮窗的显示和隐藏、通过WindowManager控制悬浮窗、添加UI组件和逻辑、实现拖拽和点击事件的响应、设置悬浮窗的类型和动画效果、在Service销毁时移除悬浮窗等步骤。 ### 回答3: 要实现Android系统的全局悬浮窗,我们可以按照以下步骤进行: 1. 添加悬浮窗权限:在AndroidManifest.xml文件中添加SYSTEM_ALERT_WINDOW权限,允许应用在其他应用上层显示视图。 2. 创建悬浮窗Service:创建一个继承自Service的类,用于管理悬浮窗的显示和隐藏。在Service的onCreate()方法中,创建悬浮窗视图,并通过WindowManager将其添加到屏幕上。 3. 定义悬浮窗视图:使用自定义的View类创建悬浮窗视图,可以根据需求设计布局和添加控件。通过设置LayoutParams参数,控制悬浮窗位置和大小。 4. 处理触摸事件:为悬浮窗视图添加触摸监听器,实现移动、缩放等手势操作。可以使用MotionEvent类获取触摸坐标,并通过更新LayoutParams参数来实现相应的操作。 5. 处理悬浮窗的生命周期:在Service的onStartCommand()方法中处理悬浮窗的显示和隐藏逻辑。在悬浮窗视图销毁时,需要将其从WindowManager中移除。 6. 处理权限申请:在Android版本为M及以上时,需要动态请求悬浮窗权限。可以通过使用Settings.ACTION_MANAGE_OVERLAY_PERMISSION打开系统设置界面,让用户手动授权。 7. 适配不同Android版本:由于不同Android版本权限管理方式不同,需要在代码中进行适配处理。可以通过Build.VERSION.SDK_INT来判断系统版本,并采取相应的操作。 通过以上步骤,我们可以实现Android系统的全局悬浮窗。在悬浮窗的显示和隐藏逻辑上,可以根据具体需求进行进一步的优化和定制,以满足不同场景下的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值