美文网首页Android开发探索安卓Android 技术开发
Android应用内悬浮窗的实现方案

Android应用内悬浮窗的实现方案

作者: C6C | 来源:发表于2017-08-01 23:58 被阅读9146次

1、悬浮窗的基本介绍

悬浮窗,大家应该也不陌生,凌驾于应用之上的一个小弹窗,实现上很简单,就是添加一个系统级别的窗口,Android中通过WindowManagerService( WMS)来管理所有的窗口,对于WMS来说,管你是Activity、Toast、Dialog,都不过是通过WindowManagerGlobal.addView()添加的一个个View。
Android中的窗口分为三个级别:

  • 1.1 应用窗口,比如Activity的窗口;
  • 1.2 子窗口,依赖于父窗口,比如PopupWindow;
  • 1.3 系统窗口,比如状态栏、Toast,目标悬浮窗就是系统窗口.

2、根据产品需求进行设计

先了解一下大概的产品需求:
1、悬浮窗需要跨越整个应用
2、需要与悬浮窗进行交互
3、悬浮窗得移动
4、点击跳转特定的页面
5、消息提示的拖拽小红点

需求很简单,但是如果估算没错,不下一周产品经理会添加新的需求,所以为了更好的后续扩展,需要进行合理的设计,主要分为以下几点:
1、悬浮窗自定义一个FrameLayout布局FloatLayout,里面进行拖动及点击响应处理;
2、FloatMonkService,是一个服务,开启服务的时候创建悬浮窗;
3、FloatCallBack,交互接口,在FloatMonkService里面实现接口,用于交互;
4、FloatWindowManager,悬浮窗的管理,因为后续悬浮窗布局可能有好几个,可以在这里面进行切换;
5、HomeWatcherReceiver,广播接收者,因为在应用内展示,需要监听用户在点击Home键和切换键的时候隐藏悬浮窗,需要FloatMonkService里头动态注册;
6、FloatActionController,其实就是代理,其它模块需要通过它来和悬浮窗进行交互,真正干活的是实现FloatCallBack接口的FloatMonkService;
7、FloatPermissionManager,需要适配各个傻逼机型的权限,庆幸网上已有大佬分享,只需要单独对7.0系统进行一些适配就行,悬浮窗权限适配;
8、拖拽控件DraggableFlagView,直接拿来在悬浮窗上出现很奇怪的问题,所以需要改造一下下才能达到图中效果。

3、具体实现

float_littlemonk_layout.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:dfv="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical">

    <RelativeLayout
        android:id="@+id/monk_relative_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/float_id"
            android:layout_width="70dp"
            android:layout_height="80dp"
            android:layout_gravity="center_vertical|end"
            android:scaleType="center"
            android:src="@drawable/little_monk" />

    </RelativeLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <floatwindow.xishuang.float_lib.view.DraggableFlagView
            android:id="@+id/main_dfv"
            android:layout_width="17dp"
            android:layout_height="17dp"
            android:layout_gravity="end"
            dfv:color1="#FF3B30" />
    </FrameLayout>
</FrameLayout>

简单的布局,就是一张图片+右上角放一个自定义的小红点。

FloatLayout.java

@Override
    public boolean onTouchEvent(MotionEvent event) {
        // 获取相对屏幕的坐标,即以屏幕左上角为原点
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        //下面的这些事件,跟图标的移动无关,为了区分开拖动和点击事件
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startTime = System.currentTimeMillis();
                mTouchStartX = event.getX();
                mTouchStartY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //图标移动的逻辑在这里
                float mMoveStartX = event.getX();
                float mMoveStartY = event.getY();
                // 如果移动量大于3才移动
                if (Math.abs(mTouchStartX - mMoveStartX) > 3
                        && Math.abs(mTouchStartY - mMoveStartY) > 3) {
                    // 更新浮动窗口位置参数
                    mWmParams.x = (int) (x - mTouchStartX);
                    mWmParams.y = (int) (y - mTouchStartY);
                    mWindowManager.updateViewLayout(this, mWmParams);
                    return false;
                }
                break;
            case MotionEvent.ACTION_UP:
                endTime = System.currentTimeMillis();
                //当从点击到弹起小于半秒的时候,则判断为点击,如果超过则不响应点击事件
                if ((endTime - startTime) > 0.1 * 1000L) {
                    isclick = false;
                } else {
                    isclick = true;
                }
                break;
        }
        //响应点击事件
        if (isclick) {
            Toast.makeText(mContext, "我是大傻叼", Toast.LENGTH_SHORT).show();
        }
        return true;
    }

为了把悬浮窗的view操作抽离出来,自定义了这个布局,主要进行两部分功能,悬浮窗的移动和点击处理,重点是通过mWindowManager.updateViewLayout(this, mWmParams)来进行悬浮窗的位置移动,我这个Demo里面只是简单的通过时间来判断点击事件,有必要的话点击事件需要添加特定View范围判断来响应点击。

// 如果移动量大于3才移动
if (Math.abs(mTouchStartX - mMoveStartX) > 3 && Math.abs(mTouchStartY - mMoveStartY) > 3) 

这个判断是为了避免点击悬浮窗不在重心位置会出现移动的现象。

FloatMonkService.java

/**
 * 悬浮窗在服务中创建,通过暴露接口FloatCallBack与Activity进行交互
 */
public class FloatMonkService extends Service implements FloatCallBack {
    /**
     * home键监听
     */
    private HomeWatcherReceiver mHomeKeyReceiver;

    @Override
    public void onCreate() {
        super.onCreate();
        FloatActionController.getInstance().registerCallLittleMonk(this);
        //注册广播接收者
        mHomeKeyReceiver = new HomeWatcherReceiver();
        final IntentFilter homeFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        registerReceiver(mHomeKeyReceiver, homeFilter);
        //初始化悬浮窗UI
        initWindowData();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 初始化WindowManager
     */
    private void initWindowData() {
        FloatWindowManager.createFloatWindow(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //移除悬浮窗
        FloatWindowManager.removeFloatWindowManager();
        //注销广播接收者
        if (null != mHomeKeyReceiver) {
            unregisterReceiver(mHomeKeyReceiver);
        }
    }

    /////////////////////////////////////////////////////////实现接口////////////////////////////////////////////////////
    @Override
    public void guideUser(int type) {
        FloatWindowManager.updataRedAndDialog(this);
    }


    /**
     * 悬浮窗的隐藏
     */
    @Override
    public void hide() {
        FloatWindowManager.hide();
    }

    /**
     * 悬浮窗的显示
     */
    @Override
    public void show() {
        FloatWindowManager.show();
    }

    /**
     * 添加可领取的数量
     */
    @Override
    public void addObtainNumer() {
        FloatWindowManager.addObtainNumer(this);
        guideUser(4);
    }

    /**
     * 减少可领取的数量
     */
    @Override
    public void setObtainNumber(int number) {
        FloatWindowManager.setObtainNumber(this, number);
    }
}

服务开启的时候通过FloatWindowManager.createFloatWindow(this)来创建悬浮窗,实现FloatCallBack 实现需要交互的接口。下面看一下创建悬浮窗的真正操作是怎样的。

FloatWindowManager.java

/**
     * 创建一个小悬浮窗。初始位置为屏幕的右下角位置。
     */
    public static void createFloatWindow(Context context) {
        wmParams = new WindowManager.LayoutParams();
        WindowManager windowManager = getWindowManager(context);
        mFloatLayout = new FloatLayout(context);
        if (Build.VERSION.SDK_INT >= 24) { /*android7.0不能用TYPE_TOAST*/
            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        } else { /*以下代码块使得android6.0之后的用户不必再去手动开启悬浮窗权限*/
            String packname = context.getPackageName();
            PackageManager pm = context.getPackageManager();
            boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SYSTEM_ALERT_WINDOW", packname));
            if (permission) {
                wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            } else {
                wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
            }
        }

        //设置图片格式,效果为背景透明
        wmParams.format = PixelFormat.RGBA_8888;
        //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
        wmParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        //调整悬浮窗显示的停靠位置为左侧置顶
        wmParams.gravity = Gravity.START | Gravity.TOP;

        DisplayMetrics dm = new DisplayMetrics();
        //取得窗口属性
        mWindowManager.getDefaultDisplay().getMetrics(dm);
        //窗口的宽度
        int screenWidth = dm.widthPixels;
        //窗口高度
        int screenHeight = dm.heightPixels;
        //以屏幕左上角为原点,设置x、y初始值,相对于gravity
        wmParams.x = screenWidth;
        wmParams.y = screenHeight;

        //设置悬浮窗口长宽数据
        wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        mFloatLayout.setParams(wmParams);
        windowManager.addView(mFloatLayout, wmParams);
        mHasShown = true;
        //是否展示小红点展示
        checkRedDot(context);
    }

/**
     * 返回当前已创建的WindowManager。
     */
    private static WindowManager getWindowManager(Context context) {
        if (mWindowManager == null) {
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }
        return mWindowManager;
    }

核心代码其实就是mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE),其中的context不能是Activity的,一开始就说了,Activity会返回它专享的WindowManager,而Activity的窗口级别是属于应用层的。进行一些初始化操作之后 windowManager.addView(mFloatLayout, wmParams)把布局添加进去就ok了。

 if (Build.VERSION.SDK_INT >= 24) { /*android7.0不能用TYPE_TOAST*/
            wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
        } else { /*以下代码块使得android6.0之后的用户不必再去手动开启悬浮窗权限*/
            String packname = context.getPackageName();
            PackageManager pm = context.getPackageManager();
            boolean permission = (PackageManager.PERMISSION_GRANTED == pm.checkPermission("android.permission.SYSTEM_ALERT_WINDOW", packname));
            if (permission) {
                wmParams.type = WindowManager.LayoutParams.TYPE_PHONE;
            } else {
                wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
            }
        }

说一下这段代码的意义,当WindowManager.LayoutParams.type设置为WindowManager.LayoutParams.TYPE_TOAST的时候,是可以跳过权限申请的,但是为毛又单独适配各个机型呢,因为我们有小米Android系统,魅族Android系统,还有华为等等Android系统,特别是产品经理的魅族,一些特殊机型上是没有效果的,所以为了更保险,得再加一份权限申请,还有一点得提一下,那就是7.0上WindowManager.LayoutParams.TYPE_TOAST,悬浮窗只能持续一秒的时间,所以7.0不设这个type,谷歌爸爸最叼,7.0以上老老实实申请权限。

FloatActionController.java


/**
 * Author:xishuang
 * Date:2017.08.01
 * Des:与悬浮窗交互的控制类,真正的实现逻辑不在这
 */
public class FloatActionController {

    private FloatActionController() {
    }

    public static FloatActionController getInstance() {
        return LittleMonkProviderHolder.sInstance;
    }

    // 静态内部类
    private static class LittleMonkProviderHolder {
        private static final FloatActionController sInstance = new FloatActionController();
    }

    private FloatCallBack mCallLittleMonk;

    /**
     * 开启服务悬浮窗
     */
    public void startMonkServer(Context context) {
        Intent intent = new Intent(context, FloatMonkService.class);
        context.startService(intent);
    }

    /**
     * 关闭悬浮窗
     */
    public void stopMonkServer(Context context) {
        Intent intent = new Intent(context, FloatMonkService.class);
        context.stopService(intent);
    }

    /**
     * 注册监听
     */
    public void registerCallLittleMonk(FloatCallBack callLittleMonk) {
        mCallLittleMonk = callLittleMonk;
    }

    /**
     * 悬浮窗的显示
     */
    public void show() {
        if (mCallLittleMonk == null) return;
        mCallLittleMonk.show();
    }

    /**
     * 悬浮窗的隐藏
     */
    public void hide() {
        if (mCallLittleMonk == null) return;
        mCallLittleMonk.hide();
    }
}

这就是暴露出来的接口,按需添加,效果大概是这样的。

HomeWatcherReceiver.java

/**
 * Author:xishuang
 * Date:2017.08.01
 * Des:一些Home建与切换键的广播监听,需要动态注册
 */

public class HomeWatcherReceiver extends BroadcastReceiver {
    private static final String TAG = "HomeWatcherReceiver";
    private static final String SYSTEM_DIALOG_FROM_KEY = "reason";
    private static final String SYSTEM_DIALOG_FROM_RECENT_APPS = "recentapps";
    private static final String SYSTEM_DIALOG_FROM_HOME_KEY = "homekey";
    private static final String SYSTEM_DIALOG_FROM_LOCK = "lock";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Log.i(TAG, "onReceive: action: " + action);
        //根据不同的信息进行一些个性操作
        if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
            String from = intent.getStringExtra(SYSTEM_DIALOG_FROM_KEY);
            Log.i(TAG, "from: " + from);
            if (SYSTEM_DIALOG_FROM_HOME_KEY.equals(from)) { //短按Home键
                Log.i(TAG, "Home Key");
                //按home键会直接关闭悬浮窗
                FloatActionController.getInstance().stopMonkServer(context);
            } else if (SYSTEM_DIALOG_FROM_RECENT_APPS.equals(from)) { //长按Home键或是Activity切换键
                Log.i(TAG, "long press home key or activity switch");
            } else if (SYSTEM_DIALOG_FROM_LOCK.equals(from)) { //锁屏操作
                Log.i(TAG, "lock");
            }
        }
    }

}

这个就是一个广播接收者,需要监听系统的一些操作,然后根据不同的操作实现自己想要的逻辑,Demo中我只是针对Home键进行了简单的处理,点击Home退到主页会直接销毁服务,看具体要求进行扩展。

接下来看一下具体的使用,先看下Activity的布局
activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="xishuang.floatwindow.MainActivity">

    <Button
        android:id="@+id/open_float"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开启悬浮窗" />

    <Button
        android:id="@+id/red_dot"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="打开小红点" />

</LinearLayout>

就是两个按钮,一个用来开启悬浮窗,一个用来进行简单的交互,展示小红点。

Mainactivity.java

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btOpenFloat = (Button) findViewById(R.id.open_float);
        Button btRedDot = (Button) findViewById(R.id.red_dot);

        assert btOpenFloat != null;
        btOpenFloat.setOnClickListener(this);
        assert btRedDot != null;
        btRedDot.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.open_float) {
            boolean isPermission = FloatPermissionManager.getInstance().applyFloatWindow(this);
            //有对应权限或者系统版本小于7.0
            if (isPermission || Build.VERSION.SDK_INT < 24) {
                //开启悬浮窗
                FloatActionController.getInstance().startMonkServer(this);
            }
        } else if (v.getId() == R.id.red_dot) {
            //开启小红点
            FloatActionController.getInstance().setObtainNumber(1);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //关闭悬浮窗
        FloatActionController.getInstance().stopMonkServer(this);
    }
}

具体使用看起来也还简单,因为逻辑都已经尽量封装和解耦了,就是在开启悬浮窗的时候,7.0版本以上必须先申请权限才能开启,7.0以下可以直接开启,因为前面已经设置WindowManager.LayoutParams.TYPE_TOAST,虽然有些特殊机型也必须申请权限,但起码先保证我的悬浮窗在大多数手机上可以先展示出来。

boolean isPermission = FloatPermissionManager.getInstance().applyFloatWindow(this);

这段代码说明,无论在哪种情况,我会先进行权限检查,双重保险。

大概效果如下:


20170201.gif

Demo:代码地址感兴趣可以看看完整的演示代码。

相关文章

网友评论

  • 泥巴天才:现在要适配6.0以上的,我打开了权限为什么还是崩溃
    Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
    C6C:看下是不是8.0以上的权限设置又出现变化了,这个项目搞得时候才适配到7.0,加油
  • lu_xiukun:默认禁掉也没有开启悬浮窗权限的地方怎么办?沙雕乐视手机
  • 9624f5c764ed:大神,请问怎么绑定拖拽的控件啊
    C6C:@女丑_6e70 特意又翻了下代码,拿到布局里面的DraggableFlagView拖拽控件,控件里头提供OnDraggableFlagViewListener类进行绑定回调的
  • 键盘上的麒麟臂:为什么要用TYPE_PHONE,这是有什么理由吗
  • b2cdb4ad1c25:大神 ,我的需求是悬浮框上的按钮来控制我 项目中音乐的播放和暂停 ,我用evenbus 来通知但是没有效果能解答下吗
    b2cdb4ad1c25:@C6C 谢谢
    C6C:不行的话可以考虑用本地广播,广播这种控制方式试过是可行的,evenbus 我们公司是禁止使用的,所以我也不知道具体原因。
  • thiagooo0:老哥你demo里面那句“我是大傻叼”惊呆我了哈哈
    C6C:哈哈哈,淡定淡定
  • Android出海:推荐个开源的悬浮窗项目,挺好的,https://github.com/YoungBill/FloatWindow
  • 叶落竹影:请问从后台再次进入app 悬浮窗还显示吗 如果要显示 需要怎么操作
    C6C:@叶落竹影 我是在主activity的onresume回调那里再显示悬浮窗的
  • Android出海:推荐个开源的悬浮窗项目,挺好的,https://github.com/YoungBill/Android-FloatWindow
  • 东城_dc43:大神。怎么在悬浮窗里面做跳转,或者弹出一个列表框啊?
    C6C:@东城_dc43 是得依赖父window才行,直接弹出是不行的,我能想到的是在点击悬浮窗的时候,把悬浮窗控件换成自己实现的布局,我看世面上一些框架也是这么实现的
    东城_dc43:@C6C 对的,就是想实现一个点击悬浮框弹出一个popupWindow的操作,但是popupWindow需要依赖activity,如何在FloatLayout里面添加一个popupWindow的操作呀,大神求指教
    C6C:@东城_dc43 实现一下悬浮窗图片的点击事件,然后做你想做的,不知道我有没有理解错
  • e0b05d56bdae:您好!
    能否制作一款Android端的
    “悬浮窗口字幕播放器”
    很多Netflix 、HBO等剧集都没有对应当地语言的字幕,其应用也不支持外挂字幕。
    您能复制作一个悬浮窗口的字幕播放器,使之悬浮在视频画面之上,就像酷狗音乐的悬浮窗口歌词一样,再加上调节字幕延迟的按钮。
    (我测试了酷狗的桌面歌词何以正常覆盖在视频之上播放和交互。)
    我想这个软件如果投入Google play 一定会有很多人使用的!我愿意购买!!或者花钱请您制作。热切盼望取得联系!
    e0b05d56bdae:@大汉昭暄 telegram:liu zhaoxuan
  • 12983f0c89d8:单个应用可以悬浮,多个应用是否可以共用一个悬浮窗,如果提供全局的悬浮窗,楼主是否知道怎么做?
    C6C:@Eric_e276 想法很溜,用广播跨进程来操控,在一个app中打开,其它app发广播控制,不知道可行度怎样
    12983f0c89d8:@C6C ,我知道悬浮窗的两种显示:可以显示在一个应用中,也可以显示在整个应用之上,我有这样的一个需求,每个app都可以操作到这个悬浮窗,并且可以在悬浮窗上播放视频,比如一个app需要悬浮窗则显示,不需要的话,app退出,悬浮窗沉于窗口下面,即z=0;另一个app则可以操作同样的悬浮窗,这样就可以随意合成,随意使用了
    C6C:@Eric_e276 悬浮窗是可以展示在其它应用上面的,就我这个Demo是特意限制在一个应用内的,HomeWatcherReceiver这个广播接收类来控制的
  • 12983f0c89d8:你好,咨询一下,android N能否多个app共用一个悬浮窗呢?
  • eab751c88d02:能不能不让手机提示你没有开启悬浮窗权限的提示框呀?默认就开启了悬浮窗
    C6C:@心心点灯_02d5 有的机型是不支持默认开启的,所以开启权限的提示是必须的
    eab751c88d02:@C6C 试了两款 华为 和小米都是要有开启悬浮窗的提示
    C6C:不是所有的手机都支持默认就开启悬浮窗,不然就不需要进行权限处理了
  • koala_:弹出Dialog不会覆盖浮窗怎么办?
    C6C:@koala_ 是不会覆盖啊,悬浮窗是处于最上层的,要不在弹出Dialog的时候隐藏掉它,要不就和产品经理讲下道理,也有可能有我不知道的操作
  • eab751c88d02:大神 问下我将你的项目倒进eclipse运行之后 两个按钮点击都是没有反映的 是什么原因啊
    C6C:@心心点灯_02d5 没办法了,只能你自己排查下原因了,eclipse已经一年多没用了,加油:joy:
    eab751c88d02:@C6C 没有报错,我在studio运行是可以的,但是在eclipse运行不可以,小米手机的权限也开了!模拟器上点击也没有反映,就是那个小和尚的图片显示不出来的。开发需要吧 需要eclipse额
    C6C:看一下是不是你手机的权限问题,手动开启一下权限,还不行看下报错日志,建议用Studio开发
  • b13d64bd73ce:小米设备6.0系统的有的设备可以直接显示有的设备会提醒要要求权限,这个需要怎么处理
    b13d64bd73ce:@C6C 我的意思是WindowManager.LayoutParams.TYPE_TOAST在小米的设备上也有不能显示的问题
    C6C:wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;这段代码就是6.0以上没有权限也可以显示悬浮窗的操作,但是我的项目逻辑是就算没有权限也可以弹出,也让用户同意这个权限,如果你不想6.0弹出来权限申请,可以过滤掉,但是为了避免国产手机搞幺蛾子,我还是建议不这么干
  • 秋刀鱼_87c6:大神,无法设置悬浮窗开启时的初始位置,我看代码有设置,但是没有效用
    b2cdb4ad1c25:大神 ,我的需求是悬浮框上的按钮来控制我 项目中音乐的播放和暂停 ,我用evenbus 来通知但是没有效果能解答下吗
    C6C:FloatWindowManager->createFloatWindow->wmParams.x = screenWidth;wmParams.y = screenHeight;(以屏幕左上角为原点,设置x、y初始值,这个是可以设定初始位置的,我现在是按照屏幕的宽和高来布置到右下角)

本文标题:Android应用内悬浮窗的实现方案

本文链接:https://www.haomeiwen.com/subject/enmslxtx.html