序言

Android悬浮窗的实现,主要有四个步骤:
1. 声明及申请权限
2. 构建悬浮窗需要的控件
3. 将控件添加到WindowManager
4. 必要时更新WindowManager的布局

一、权限申请

需要在 AndroidMainfest.xml 中声明权限

在6.0以上的时候(现在基本都是6.0以上的了),还需要用户手动打开悬浮窗权限。

    int REQUEST_CODE = 520 ;      Intent intent= new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));    startActivityForResult(intent, REQUEST_CODE);

因为会跳转到设置界面,所以最好先弹出提示框,待用户点击确定后再进行跳转。

具体代码,可以参考一下:

private static final int RESULT_CODE_BOX = 10023;    @TargetApi(Build.VERSION_CODES.M)    private void checkBoxPower() {        if (!Settings.canDrawOverlays(mContext)) {            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + mContext.getPackageName()));//权限申请页面            mActivity.startActivityForResult(intent, RESULT_CODE_BOX);        }    }    @TargetApi(Build.VERSION_CODES.M)    public void onActivityResult(int requestCode, int resultCode, Intent data){        if(requestCode == RESULT_CODE_BOX){            if (!Settings.canDrawOverlays(mContext)) {                Log.e(TAG,"授权失败");            } else {                Log.e(TAG,"授权成功");                //启动悬浮窗的Service                Intent intent = new Intent(mActivity, FloatingService.class);                mActivity.startService(intent);            }        }    }

Settings.canDrawOverlays 为检查是否有悬浮窗权限的方法,如果已经有权限的时候返回true。

二、悬浮窗初始化

具体代码,可以参考一下:

private WindowManager windowManager;    private WindowManager.LayoutParams layoutParams;    //悬浮窗的初始位置    private static int FloatingInitialPosition_x = 50;    private static int FloatingInitialPosition_y = 50;    //初始化悬浮窗    private void initFrame(){        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);        layoutParams = new WindowManager.LayoutParams();//windowManager的布局        if (Build.VERSION.SDK_INT >= 26) { //8.0以上只能使用 TYPE_APPLICATION_OVERLAY窗口类型来创建悬浮窗            layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;        } else {            layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;        }        layoutParams.format = PixelFormat.RGBA_8888;//设置图片格式        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;//悬浮框在布局的位置        layoutParams.x = FloatingInitialPosition_x; //初始位置的x坐标,相对于gravity        layoutParams.y = FloatingInitialPosition_y;        layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;//指定长宽        layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;                floatView = View.inflate(getApplicationContext(), R.layout.float_box_view, null);    }    //展示悬浮窗    private View floatView;    private void showFloatingWindow() {        windowManager.addView(floatView, layoutParams);    }

悬浮窗需要在别的程序之上显示,就需要通过WindowManager获取WINDOW_SERVICE系统服务,然后通过windowManager.addView把相应的view添加进去。
8.0以上版本时,窗口类型修改为了WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
一个最简单的悬浮窗到此就结束了。

三、拖动功能

这个功能是悬浮窗中很常见的功能,要实现这个功能就需要用到触摸事件的处理View.OnTouchListener。
具体代码,可以参考一下:

floatView.setOnTouchListener(new FloatingOnTouchListener()); //悬浮窗的滑动监听    //滑动监听    private class FloatingOnTouchListener implements View.OnTouchListener {        private int x;        private int y;        @Override        public boolean onTouch(View view, MotionEvent event) {            switch (event.getAction()) {                case MotionEvent.ACTION_DOWN:                    x = (int) event.getRawX();                    y = (int) event.getRawY();                    break;                case MotionEvent.ACTION_MOVE:                    int nowX = (int) event.getRawX();                    int nowY = (int) event.getRawY();                    int movedX = nowX - x;                    int movedY = nowY - y;                    x = nowX;                    y = nowY;                    layoutParams.x = layoutParams.x + movedX;                    layoutParams.y = layoutParams.y + movedY;                    windowManager.updateViewLayout(floatView, layoutParams);                    break;                default:                    break;            }            return false;        }    }

监听滑动做成xy的变化,并刷新windowManager就可以了。
这里有一点要注意的是,如果同时使用OnTouchListener和setOnClickListener,那每次移动都会同时触发点击事件,所以这里需要做一个判断。

三、只在应用内显示

悬浮窗不需要在别的程序上显示,也不需要在桌面上显示,只需要它在本APP内显示就可以了。
其中的一种方法就是监听App是否在前台,如果在,则显示。如果不在则隐藏。
App不在前台了,可能有三种情况:1、正常退出。2、home键回到主界面了。3、点击任务键切换到别的程序。
正常退出的情况,只要我们也正常销毁悬浮窗就好。
home键和多任务键的情况,需要对按键进行监听。然后判断当前App是否在前台,如果不是,则是隐藏/销毁悬浮窗。
判断当前App是否在前台的代码可以参考:

public static boolean isAppOnForeground(Context context) {        ActivityManager activityManager = (ActivityManager) context.getApplicationContext()                .getSystemService(Context.ACTIVITY_SERVICE);        String packageName = context.getApplicationContext().getPackageName();        //获取Android设备中所有正在运行的App        List appProcesses = activityManager.getRunningAppProcesses();        if (appProcesses == null)            return false;        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {            if (appProcess.processName.equals(packageName)                    && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {                return true;            }        }        return false;    }

home键和多任务键的监听代码,可以参考:

InnerRecevier innerReceiver = new InnerRecevier();        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);        registerReceiver(innerReceiver, intentFilter);    class InnerRecevier extends BroadcastReceiver {        final String SYSTEM_DIALOG_REASON_KEY = "reason";        final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";        final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";        @Override        public void onReceive(Context context, Intent intent) {            String action = intent.getAction();            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {                String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);                if (reason != null) {                    if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {                        Toast.makeText(MainActivity.this, "Home键被监听", Toast.LENGTH_SHORT).show();                    } else if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {                        Toast.makeText(MainActivity.this, "多任务键被监听", Toast.LENGTH_SHORT).show();                    }                }            }        }    }

另一种方法是使用ViewGroup。 ViewGroup是不需要权限申请的,直接显示。( ViewGroup方法 和上面的代码是没有关联的)
如果是为unity游戏做的Android SDK,可以直接获取最顶层的view,然后把悬浮窗的view加载进来。因为unity游戏大多只有一个Activity。
具体代码,可以参考一下:(这个也可以作为广告的容器来填充广告,这段示例代码当时就是用来填充广告的)

    ViewGroup view = (ViewGroup )AppActivity.getWindow().getDecorView();//获取最顶层的view    ViewGroup bannerContainer=(ViewGroup) View.inflate(AppActivity, R.layout.activity_banner_bottom, view).findViewById(R.id.bannerContainer);//为其添加一个布局    bannerContainer.addView(adVeiw);//把悬浮窗的view加进来

如果是普通的APP,并不只有一个Activity。所以需要使用到Application.ActivityLifecycleCallbacks方法。
ActivityLifecycleCallbacks为Activity生命周期监控接口的方法,它包含了一整套Activity的生命周期回调方法,只要有一个Activity触发了声明周期,这个接口的回调就会触发,并且传回触发生命周期方法的Activity对象。
在拿到当前显示的Activity之后,通过activity.getWindow().getDecorView() 来获取Activity的视图组。然后把悬浮窗的view加进去。切换Activity时,把悬浮窗的view销毁,在新显示的Activity同样把悬浮窗的view加进去。

示例源码

更多相关文章

  1. RxJava使用(一)基本使用
  2. Android(安卓)添加系统服务的方法
  3. android studio 错误: 找不到符号 符号: 方法 xxx() 位置: 类 xx
  4. 【Android(安卓)NDK】(一)Hello World!
  5. Android(安卓)实现圆形的 ImageView 的3种方法
  6. android 多击事件的实现方法
  7. Fragment详解
  8. 【Android】xml文件里面出现unbound prefix的问题
  9. Android(安卓)SQLite数据库增删改查操作

随机推荐

  1. ubuntu NDK 的安装
  2. Sending email without user interaction
  3. Android(安卓)中文 API (35) —— ImageSwi
  4. Android(安卓)EditText不弹出软键盘
  5. android开发 列表显示(ListView)
  6. android 电容屏(二):驱动调试之基本概念篇
  7. Android(安卓)后台任务(五)Service
  8. ImageView的scaletype属性
  9. 链接器解析多重定义的全局变量
  10. android 来电自动接听和自动挂断