安卓基础悬浮窗实现


前言

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

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

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

manifest文件添加权限:

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

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

  if (!Settings.canDrawOverlays(this)) {
                    Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
                    startActivity(intent);
                }

在这里插入图片描述

二、通过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;
        }
// 设置悬浮框不可触摸
 params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

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;
      }
  });

三、完整代码

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

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;
        }
    }
}

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();
    }
}

别忘了在Manifest文件声明service

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

看一下实现效果:

在这里插入图片描述

后续增加缩放+MPAndroidChart效果。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
安卓悬浮窗实现多触点拖动的方法如下: 1. 首先,需要创建一个悬浮窗的布局文件,例如`float_window.xml`,并在其中定义一个可拖动的视图,例如一个`ImageView`。 2. 在悬浮窗的服务类中,通过`WindowManager`来添加悬浮窗,并设置其布局参数。 3. 在悬浮窗的服务类中,通过触摸事件监听器来实现多触点拖动的功能。在触摸事件监听器中,可以通过`MotionEvent`对象获取到触摸事件的坐标信息,并根据坐标信息来更新悬浮窗的位置。 4. 在悬浮窗的服务类中,需要处理悬浮窗的点击事件,以便实现其他功能,例如点击悬浮窗打开应用程序等。 下面是一个示例代码,演示了如何实现安卓悬浮窗的多触点拖动: ```java // 创建悬浮窗的布局文件 float_window.xml <ImageView android:id="@+id/float_view" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/float_icon" /> // 在悬浮窗的服务类中实现多触点拖动 public class FloatWindowService extends Service { private WindowManager windowManager; private WindowManager.LayoutParams layoutParams; private ImageView floatView; private int lastX, lastY; private int screenWidth, screenHeight; @Override public void onCreate() { super.onCreate(); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); layoutParams = new WindowManager.LayoutParams(); layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; layoutParams.format = PixelFormat.RGBA_8888; layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; layoutParams.gravity = Gravity.START | Gravity.TOP; layoutParams.x = 0; layoutParams.y = 0; layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; floatView = new ImageView(this); floatView.setImageResource(R.drawable.float_icon); windowManager.addView(floatView, layoutParams); DisplayMetrics dm = new DisplayMetrics(); windowManager.getDefaultDisplay().getMetrics(dm); screenWidth = dm.widthPixels; screenHeight = dm.heightPixels; floatView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int dx = (int) event.getRawX() - lastX; int dy = (int) event.getRawY() - lastY; layoutParams.x += dx; layoutParams.y += dy; if (layoutParams.x < 0) { layoutParams.x = 0; } if (layoutParams.y < 0) { layoutParams.y = 0; } if (layoutParams.x > screenWidth - floatView.getWidth()) { layoutParams.x = screenWidth - floatView.getWidth(); } if (layoutParams.y > screenHeight - floatView.getHeight()) { layoutParams.y = screenHeight - floatView.getHeight(); } windowManager.updateViewLayout(floatView, layoutParams); lastX = (int) event.getRawX(); lastY = (int) event.getRawY(); break; case MotionEvent.ACTION_UP: // 处理点击事件 break; } return true; } }); } @Override public void onDestroy() { super.onDestroy(); if (floatView != null) { windowManager.removeView(floatView); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ``` 请注意,为了实现悬浮窗的多触点拖动,需要在AndroidManifest.xml文件中添加悬浮窗权限: ```xml <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值