显示悬浮窗口

显示悬浮窗口可以使用WindowManager的addView方法,为了关闭应用能够继续运行,打开应用的时候开启一个Service,让Service去设置和打开悬浮窗口。

WindowManager主要是使用3个方法:

addView方法用于添加一个悬浮窗,updateViewLayout方法用于更新悬浮窗的参数,removeView用于移除悬浮窗。

WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数,其中有几个经常会用到的变量:

type值用于确定悬浮窗的类型,一般设为2002,表示在所有应用程序之上,但在状态栏之下。

flags值用于确定悬浮窗的行为,比如说不可聚焦,非模态对话框等等,属性非常多,大家可以查看文档。

gravity值用于确定悬浮窗的对齐方式,一般设为左上角对齐,这样当拖动悬浮窗的时候方便计算坐标。

x值用于确定悬浮窗的位置,如果要横向移动悬浮窗,就需要改变这个值。

y值用于确定悬浮窗的位置,如果要纵向移动悬浮窗,就需要改变这个值。

width值用于指定悬浮窗的宽度。

height值用于指定悬浮窗的高度。

创建悬浮窗这种窗体需要向用户申请权限才可以的,因此还需要在AndroidManifest.xml中加入uses-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW”

代码

如图,这个蓝色的图标就是悬浮框了

这里写图片描述

打开Activity时启动FloatingWindowService

  Intent intent = new Intent(MainActivity.this,FloatingWindowService.class);
                startService(intent);

FloatingWindowService代码:

package com.flyelephant.notebook.service;

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;

import com.flyelephant.notebook.R;
import com.flyelephant.notebook.constant.PreferenceConst;
import com.panda.commonlibrary.utils.Logout;
import com.panda.commonlibrary.utils.PreferencesUtils;

public class FloatingWindowService extends Service {
    private WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    private static WindowManager windowManager;
    private static ImageView imageView;

    public FloatingWindowService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Logout.e("onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logout.e("onStartCommand");

        // 判断UI控件是否存在
        if (imageView == null) {

            // 1、获取系统级别的WindowManager
            windowManager = (WindowManager) getApplication().getSystemService(WINDOW_SERVICE);
            // 2、使用Application context 创建UI控件,避免Activity销毁导致上下文出现问题
            imageView = new ImageView(this);
            imageView.setImageResource(R.mipmap.logo);


            // 3、设置系统级别的悬浮窗的参数,保证悬浮窗悬在手机桌面上
            // 系统级别需要指定type 属性
            // TYPE_SYSTEM_ALERT 允许接收事件
            // TYPE_SYSTEM_OVERLAY 悬浮在系统上
            // 注意清单文件添加权限

            //系统提示。它总是出现在应用程序窗口之上。
            lp.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
                    | WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;

            // FLAG_NOT_TOUCH_MODAL不阻塞事件传递到后面的窗口
            // FLAG_NOT_FOCUSABLE 悬浮窗口较小时,后面的应用图标由不可长按变为可长按,不设置这个flag的话,home页的划屏会有问题
            lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;

            lp.gravity = Gravity.LEFT | Gravity.TOP;

            //显示位置与指定位置的相对位置差
            lp.x = 0;
            lp.y = 0;

            //取出之前的位置
            int fx = PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).getInt(PreferenceConst.FLOWINGWINDOW_X, 0);
            int fy = PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).getInt(PreferenceConst.FLOWINGWINDOW_Y, 0);
            Logout.e("fx:" + fx);
            Logout.e("fy:" + fy);

            //如果两个都是默认的数值,则认为是第一次使用
            if (fx == 0 && fy == 0) {
                //悬浮窗默认显示的位置
                lp.gravity = Gravity.LEFT | Gravity.TOP;
                //显示位置与指定位置的相对位置差
                lp.x = 0;
                lp.y = 0;
            } else {
                lp.x = fx;
                lp.y = fy;
            }

            //悬浮窗的宽高
            lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
            lp.height = WindowManager.LayoutParams.WRAP_CONTENT;

            lp.format = PixelFormat.TRANSPARENT;
            windowManager.addView(imageView, lp);

            //设置悬浮窗监听事件
            imageView.setOnTouchListener(new View.OnTouchListener() {
                private float lastX; //上一次位置的X.Y坐标
                private float lastY;
                private float nowX;  //当前移动位置的X.Y坐标
                private float nowY;
                private float tranX; //悬浮窗移动位置的相对值
                private float tranY;

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    boolean ret = false;
                    switch (event.getAction()) {
                        case MotionEvent.ACTION_DOWN:
                            // 获取按下时的X,Y坐标
                            lastX = event.getRawX();
                            lastY = event.getRawY();
                            ret = true;
                            break;
                        case MotionEvent.ACTION_MOVE:
                            // 获取移动时的X,Y坐标
                            nowX = event.getRawX();
                            nowY = event.getRawY();
                            // 计算XY坐标偏移量
                            tranX = nowX - lastX;
                            tranY = nowY - lastY;
                            // 移动悬浮窗
                            lp.x += tranX;
                            lp.y += tranY;
                            //更新悬浮窗位置
                            windowManager.updateViewLayout(imageView, lp);
                            //记录当前坐标作为下一次计算的上一次移动的位置坐标
                            lastX = nowX;
                            lastY = nowY;

                            break;
                        case MotionEvent.ACTION_UP:
                            //使用SharedPreferences记录当前的位置
                            PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).put(PreferenceConst.FLOWINGWINDOW_X, lp.x);
                            PreferencesUtils.getPreferences(FloatingWindowService.this, PreferenceConst.FILENAME).put(PreferenceConst.FLOWINGWINDOW_Y, lp.y);
                            Logout.e("lp.x:" + lp.x);
                            Logout.e("lp.y:" + lp.y);
                            break;
                    }
                    return ret;
                }
            });
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {

        return null;
    }
}

PreferencesUtils工具

package com.panda.commonlibrary.utils;

import android.content.Context;
import android.content.SharedPreferences;

import java.util.Map;
import java.util.Set;

/**
 * 轻量级数据存储工具
 *
 */
public class PreferencesUtils {
    static PreferencesUtils singleton = null;
    static SharedPreferences preferences;
    static SharedPreferences.Editor editor;

    private PreferencesUtils(Context context, String fileName) {
        preferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE);
        editor = preferences.edit();
    }

    public static PreferencesUtils getPreferences(Context context, String fileName) {
        if (singleton == null) {
            singleton = new Builder(context, fileName).build();
        }
        return singleton;
    }

    public void put(String key, String value) {
        editor.putString(key, value);
        editor.commit();
    }

    public void put(String key, boolean value) {
        editor.putBoolean(key, value);
        editor.commit();
    }

    public void put(String key, float value) {
        editor.putFloat(key, value);
        editor.commit();
    }

    public void put(String key, int value) {
        editor.putInt(key, value);
        editor.commit();
    }

    public void put(String key, Long value) {
        editor.putLong(key, value);
        editor.commit();
    }

    public void put(String key, Set<String> values) {
        editor.putStringSet(key, values);
        editor.commit();
    }

    public String getString(String key, String defValue) {
        return preferences.getString(key, defValue);
    }

    public int getInt(String key, int defValue) {
        return preferences.getInt(key, defValue);
    }

    public boolean getBoolean(String key, boolean defValue) {
        return preferences.getBoolean(key, defValue);
    }

    public long getLong(String key, long defValue) {
        return preferences.getLong(key, defValue);
    }

    public float getFloat(String key, float defValue) {
        return preferences.getFloat(key, defValue);
    }

    public Set<String> getStringSet(String key, Set<String> defValues) {
        return preferences.getStringSet(key, defValues);
    }

    public Map<String, ?> getAll() {
        return preferences.getAll();
    }

    public void remove(String key) {
        editor.remove(key).apply();
    }

    public void clear() {
        editor.clear().apply();
    }

    private static class Builder {
        private final Context context;
        private final String fileName;

        public Builder(Context context, String fileName) {
            if (context == null)
                throw new IllegalArgumentException("Context must not be null.");
            this.context = context.getApplicationContext();
            this.fileName = fileName;
        }

        /**
         * Method that creates an instance of Prefs
         *
         * @return an instance of Prefs
         */
        public PreferencesUtils build() {
            return new PreferencesUtils(context, fileName);
        }
    }

}

常量,FILENAME是保存的xml文件名称

public class PreferenceConst {
    public  static  String FILENAME="flyelephant";

    public  static  String FLOWINGWINDOW_X="FlowingWindow_X";
    public  static  String FLOWINGWINDOW_Y="FlowingWindow_Y";

}

Manifest文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.flyelephant.notebook">

    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- 在SDCard中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <!-- 往SDCard写入数据权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    <application
        android:name=".APPApplication"
        android:allowBackup="true"
        android:icon="@mipmap/logo"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/logo"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service
            android:name=".service.FloatingWindowService"
     >
        </service>
    </application>

</manifest>

效果图:

参考

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值