当前位置:   article > 正文

安卓基础悬浮窗实现

安卓基础悬浮窗实现


前言

记录一下基础的悬浮窗实现,分为几个重要的点进行阐述。

一、添加对悬浮窗功能的支持

app要实现悬浮窗功能,首先app要添加对悬浮窗功能的支持。

manifest文件添加权限:

   <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  • 1

app内也要去进行界面跳转,在设置里打开该应用的悬浮窗权限支持。

  if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivity(intent);
                }
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

二、通过service实现悬浮窗

通过Button开启服务的方式来实现悬浮窗,使用Windowmanager添加悬浮窗View。

2.1 窗口属性和标志

悬浮窗的属性一般为TYPE_APPLICATION_OVERLAY、TYPE_SYSTEM_ALERT、TYPE_TOAST、TYPE_APPLICATION_OVERLAY 等,这里采用TYPE_APPLICATION_OVERLAY 赋予悬浮窗基本属性。

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
  • 1
  • 2
  • 3
  • 4
  • 5
// 设置悬浮框不可触摸
 params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  • 1
  • 2
  • 3

2.2 窗口移动

对view进行ontouch事件的重写,更新坐标即可

  // 设置悬浮框的Touch监听
  btnView.setOnTouchListener(new View.OnTouchListener() {
      //保存悬浮框最后位置的变量
      int lastX, lastY;
      int paramX, paramY;

      @Override
      public boolean onTouch(View v, MotionEvent event) {
          switch (event.getAction()) {
              case MotionEvent.ACTION_DOWN:
                  lastX = (int) event.getRawX();
                  lastY = (int) event.getRawY();
                  paramX = params.x;
                  paramY = params.y;
                  break;
              case MotionEvent.ACTION_MOVE:
                  int dx = (int) event.getRawX() - lastX;
                  int dy = (int) event.getRawY() - lastY;
                  params.x = paramX + dx;
                  params.y = paramY + dy;
                  // 更新悬浮窗位置
                  windowManager.updateViewLayout(btnView, params);
                  break;
          }
          return true;
      }
  });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

三、完整代码

确实挺简单的,没什么可讲的。

activity

public class WindowActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn_on;
    Button btn_off;
    Boolean isOpen = false;
    Intent mIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_window);
        bindViews();
    }

    private void bindViews() {
        btn_on = findViewById(R.id.btn_on);
        btn_on.setOnClickListener(this);
        btn_off = findViewById(R.id.btn_off);
        btn_off.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_on:
                mIntent = new Intent(WindowActivity.this, FloatService.class);
                mIntent.putExtra(FloatService.OPERATION, FloatService.OPERATION_SHOW);
                if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivity(intent);
                } else {
                    startService(mIntent);
                    Toast.makeText(WindowActivity.this, "悬浮框已开启~", Toast.LENGTH_SHORT).show();
                    isOpen = true;
                }
                break;
            case R.id.btn_off:
                if (isOpen) {
                    stopService(mIntent);
                    isOpen = false;
                }
                Toast.makeText(WindowActivity.this, "悬浮框已关闭~", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isOpen) {
            stopService(mIntent);
            isOpen = false;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

FloatService

public class FloatService extends Service {
    Button btnView;
    WindowManager windowManager;
    WindowManager.LayoutParams params;
    Boolean isAdded;
    public static String OPERATION = "是否需要开启";
    public static int OPERATION_SHOW = 1;
    public static int OPERATION_HIDE = 2;
    int HANDLE_CHECK_ACTIVITY = 0;

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        int operation = intent.getIntExtra(OPERATION, 3);
        if (operation == OPERATION_SHOW) {
            mHandler.sendEmptyMessage(HANDLE_CHECK_ACTIVITY);
        } else if (operation == OPERATION_HIDE) {
            mHandler.removeMessages(HANDLE_CHECK_ACTIVITY);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onCreate() {
        createWindowView();
        super.onCreate();
    }

    @SuppressLint("ClickableViewAccessibility")
    private void createWindowView() {
        btnView = new Button(getApplicationContext());
        btnView.setBackgroundResource(R.drawable.author);
        windowManager = (WindowManager) getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE);
        params = new WindowManager.LayoutParams();

        // 设置悬浮框不可触摸
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        // 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应
        params.format = PixelFormat.RGBA_8888;
        // 设置悬浮框的宽高
        params.width = 200;
        params.height = 200;
        params.gravity = Gravity.LEFT;
        params.x = 200;
        params.y = 000;
        // 设置Window Type
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            params.type = WindowManager.LayoutParams.TYPE_PHONE;
        }
        // 设置悬浮框的Touch监听
        btnView.setOnTouchListener(new View.OnTouchListener() {
            //保存悬浮框最后位置的变量
            int lastX, lastY;
            int paramX, paramY;

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();
                        paramX = params.x;
                        paramY = params.y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int dx = (int) event.getRawX() - lastX;
                        int dy = (int) event.getRawY() - lastY;
                        params.x = paramX + dx;
                        params.y = paramY + dy;
                        // 更新悬浮窗位置
                        windowManager.updateViewLayout(btnView, params);
                        break;
                }
                return true;
            }
        });
        windowManager.addView(btnView, params);
        isAdded = true;
    }

    /**
     * 判断当前界面是否是桌面
     * android 6.0以上只能判断当前应用包名和Launcher
     */
    private boolean isAtHome() {
        ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> runningTaskInfos = mActivityManager.getRunningTasks(1);
        Log.d("henry", "是否在主页面" + runningTaskInfos);
        return getHomeApplicationList().contains(runningTaskInfos.get(0).topActivity.getPackageName());
    }

    /**
     * 获得属于桌面的应用的应用包名称
     *
     * @return 返回包含所有包名的字符串列表
     */

    /**
     * 获得属于桌面的应用的应用包名称
     * 返回包含所有包名的字符串列表数组
     *
     * @return
     */
    private List<String> getHomeApplicationList() {
        List<String> names = new ArrayList<String>();
        PackageManager packageManager = this.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resolveInfos) {
            names.add(resolveInfo.activityInfo.packageName);
        }
        Log.d("henry", "主屏幕应用列表" + names);
        return names;
    }

    //定义一个更新界面的Handler
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == HANDLE_CHECK_ACTIVITY) {
//                if (isAtHome()) {
                if (!isAdded) {
                    windowManager.addView(btnView, params);
                    isAdded = true;
                    new Thread(new Runnable() {
                        public void run() {
                            for (int i = 0; i < 10; i++) {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                Message m = new Message();
                                m.what = 2;
                                mHandler.sendMessage(m);
                            }
                        }
                    }).start();
                }
//                } else {
//                    if (isAdded) {
//                        windowManager.removeView(btnView);
//                        isAdded = false;
//                    }
//                }
                mHandler.sendEmptyMessageDelayed(HANDLE_CHECK_ACTIVITY, 100);
            }
        }
    };

    @Override
    public void onDestroy() {
        if (isAdded) {
            windowManager.removeView(btnView);
        }
        mHandler.removeCallbacksAndMessages(null);
        windowManager = null;
        mHandler = null;
        super.onDestroy();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172

别忘了在Manifest文件声明service

        <service android:name="com.henry.windowManagerTest.My_Floating_Window.FloatService" />
  • 1

看一下实现效果:

在这里插入图片描述

后续增加缩放+MPAndroidChart效果。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/591467
推荐阅读
相关标签
  

闽ICP备14008679号