Android桌面悬浮窗口举例

本文介绍了如何在Android中开发桌面悬浮窗口,通过WindowManager的addView方法添加视图,并使用service避免被其他界面阻挡。提供了关键代码示例,包括布局文件、View类、WindowManager管理类和服务类。同时提醒开发者注意添加ACTION_MANAGE_OVERLAY_PERMISSION权限。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

Android项目开发时,有时候需要开发一些悬浮在桌面上的视图。比如桌面小精灵,各种音乐播放器的悬浮播放控制栏等等。本文就借助一个小的demo,用代码的方式大概进行介绍。
桌面悬浮窗口

原理

开发桌面悬浮窗口一般遵循两个大的原则。
1.最根本的原则是采用WindowManager类,WindowManager有addView(View view, ViewGroup.LayoutParams params)方法,改方法可以直接在Android根窗口布局中添加一个View视图。这里的view其实就是我们要在桌面上显示的视图。
同理,如果我们要让这个视图消失掉,就可以调用WindowManager的removeView(View view)方法。
2.另外一个原则是,需要借助service。因为service是没有界面的,如果在activity中进行的话,当前的界面会挡住这个悬浮框。
好了,话不多说直接上代码。

关键代码展示

需要展示的View视图的布局layout_float_window.xml

<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/small_window_layout"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:background="#f4f2f3"
    android:paddingLeft="@dimen/margin_20"
    android:paddingRight="@dimen/margin_20">

    <ImageView
        android:layout_width="@dimen/margin_60"
        android:layout_height="@dimen/margin_60"
        android:layout_gravity="center_vertical"
        android:src="@drawable/ic_microphone_white" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="70dp"
        android:layout_marginRight="50dp"
        android:gravity="center_vertical"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:lineSpacingExtra="0sp"
            android:text="咪咕家语音播报"
            android:textColor="#282e33"
            android:textSize="17sp" />

        <TextView
            android:id="@+id/percent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="7dp"
            android:lineSpacingExtra="0sp"
            android:text="IM消息"
            android:textColor="#66737f"
            android:textSize="14sp" />
    </LinearLayout>

    <ImageView
        android:id="@+id/iv_close_notify"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical|right"
        android:scaleType="center"
        android:src="@drawable/ic_close_grey1" />
</FrameLayout>

需要展示的View视图类FloatWindow.java

public class FloatWindow extends LinearLayout {

    public static int viewWidth;  //悬浮窗的宽度
    public static int viewHeight; //悬浮窗的高度
    private static int statusBarHeight; //系统状态栏的高度
    private WindowManager windowManager; //用于更新悬浮窗的位置
    private WindowManager.LayoutParams mParams; //悬浮窗的参数
    private float xInScreen; //当前手指位置在屏幕上的横坐标值
    private float yInScreen; //当前手指位置在屏幕上的纵坐标值
    private float xDownInScreen; //手指按下时在屏幕上的横坐标的值
    private float yDownInScreen; //手指按下时在屏幕上的纵坐标的值
    private float xInView; //手指按下时在悬浮窗的View上的横坐标的值
    private float yInView;//手指按下时在悬浮窗的View上的纵坐标的值

    private TextView tittleView;  //标题
    private TextView contentView; //内容
    private ImageView closeView;  //关闭按钮

    public FloatWindow(final Context context) {
        super(context);
        windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        LayoutInflater.from(context).inflate(R.layout.layout_float_window, this);
        View view = findViewById(R.id.small_window_layout);
        viewWidth = view.getLayoutParams().width;
        viewHeight = view.getLayoutParams().height;
        contentView = (TextView) findViewById(R.id.percent);
        contentView.setText("IM消息");
        closeView = (ImageView) findViewById(R.id.iv_close_notify);
        closeView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                FloatWindowManager.removeFloatWindow(context);
                Intent intent = new Intent(getContext(), VoiceBroadcastService.class);
                context.stopService(intent);
            }
        });
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
                xInView = event.getX();
                yInView = event.getY();
                xDownInScreen = event.getRawX();
                yDownInScreen = event.getRawY() - getStatusBarHeight();
                xInScreen = event.getRawX();
                yInScreen = event.getRawY() - getStatusBarHeight();
                break;
            case MotionEvent.ACTION_MOVE:
                xInScreen = event.getRawX();
                yInScreen = event.getRawY() - getStatusBarHeight();
                updateViewPosition();
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return true;
    }

    /**
     * 将悬浮窗的参数传入,用于更新悬浮窗的位置。
     *
     * @param params 悬浮窗的参数
     */
    public void setParams(WindowManager.LayoutParams params) {
        mParams = params;
    }

    /**
     * 更新悬浮窗在屏幕中的位置(手指拖动)
     */
    private void updateViewPosition() {
        mParams.x = (int) (xInScreen - xInView);
        mParams.y = (int) (yInScreen - yInView);
        windowManager.updateViewLayout(this, mParams);
    }

    /**
     * 用于获取状态栏的高度。
     *
     * @return 返回状态栏高度的像素值。
     */
    private int getStatusBarHeight() {
        if (statusBarHeight == 0) {
            try {
                Class<?> c = Class.forName("com.android.internal.R$dimen");
                Object o = c.newInstance();
                Field field = c.getField("status_bar_height");
                int x = (Integer) field.get(o);
                statusBarHeight = getResources().getDimensionPixelSize(x);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return statusBarHeight;
    }

}

Windowmanager管理类,该类是悬浮窗口的主要操作类。可以在桌面添加和删除窗口,并设置窗口必要的显示参数。FloatWindowManager.java

public class FloatWindowManager {

    private static FloatWindow mFloatWindow;  //悬浮窗View的实例
    private static LayoutParams mWindowLayoutParams; //悬浮窗View的参数
    private static WindowManager mWindowManager; //用于控制在屏幕上添加或移除悬浮窗

    /**
     * 创建一个悬浮窗
     * 初始位置为屏幕的右部中间位置。
     * @param context 必须为应用程序的Context.
     */
    public static void createFloatWindow(Context context) {
        WindowManager windowManager = getWindowManager(context);
        int screenWidth = windowManager.getDefaultDisplay().getWidth();
        int screenHeight = windowManager.getDefaultDisplay().getHeight();
        if (mFloatWindow == null) {
            mFloatWindow = new FloatWindow(context);
            if (mWindowLayoutParams == null) {
                mWindowLayoutParams = new LayoutParams();
                mWindowLayoutParams.type = LayoutParams.TYPE_PHONE;
                mWindowLayoutParams.format = PixelFormat.RGBA_8888;
                mWindowLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
                mWindowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
                mWindowLayoutParams.width = FloatWindow.viewWidth;
                mWindowLayoutParams.height = FloatWindow.viewHeight;
                mWindowLayoutParams.x = screenWidth;
                mWindowLayoutParams.y = screenHeight / 2;
            }
            mFloatWindow.setParams(mWindowLayoutParams);
            //最为关键的一步操作,该操作之后就可以在桌面上看到视图
            windowManager.addView(mFloatWindow, mWindowLayoutParams); 
        }
    }

    /**
     * 将悬浮窗从屏幕上移除。
     *
     * @param context 必须为应用程序的Context.
     */
    public static void removeFloatWindow(Context context) {
        if (mFloatWindow != null) {
            WindowManager windowManager = getWindowManager(context);
            windowManager.removeView(mFloatWindow);
            mFloatWindow = null;
        }
    }

    /**
     * 更新悬浮窗文字显示
     * @param context
     * @param tittle
     * @param content
     */
    public static void updateFloatShow(Context context,String tittle,String content) {
        if (mFloatWindow != null) {
            TextView percentView = (TextView) mFloatWindow.findViewById(R.id.percent);
            percentView.setText("IM消息");
        }
    }

    public static boolean isWindowShowing() {
        return mFloatWindow != null;
    }

    private static WindowManager getWindowManager(Context context) {
        if (mWindowManager == null) {
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        }
        return mWindowManager;
    }

}

具体测操作service类FloatWindowService.class

public class FloatWindowService extends Service {

    private Timer mTimer;
    private Handler mHandler = new Handler();

    @Override
    public void onCreate() {
        super.onCreate();
        if (mTimer == null) {
            mTimer = new Timer();
            mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
        }
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (mTimer == null) {
            mTimer = new Timer();
            mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        mTimer.cancel();
        mTimer = null;
        super.onDestroy();
    }

    class RefreshTask extends TimerTask {

        /**
         * 当前界面是桌面,且没有悬浮窗显示,则创建悬浮窗
         * 当前界面不是桌面,且有悬浮窗显示,则移除悬浮窗
         * 当前界面是桌面,且有悬浮窗显示,设置悬浮窗内容
         */
        @Override
        public void run() {
            if (WindowUtils.getInstance().isAtHome(getApplicationContext()) && !FloatWindowManager.isWindowShowing()) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        FloatWindowManager.createFloatWindow(getApplicationContext());
                    }
                });
            } else if (!WindowUtils.getInstance().isAtHome(getApplicationContext()) && FloatWindowManager.isWindowShowing()) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        FloatWindowManager.removeFloatWindow(getApplicationContext());
                    }
                });
            }
        }
    }
}

如果要开启android悬浮窗口就激活以上service即可。大致如下

Intent intent = new Intent(....this, FloatWindow.class);
        VoiceBroadcastService.voiceUrl = voicePath;
        startService(intent);

以上就是android桌面悬浮窗口的大致操作,希望能给大家带来帮助。另外,这里小小留一个悬念,就是FloatWindowService类中的isAtHome方法。这是判断当前界面是否为桌面的方法,需要的朋友可以访问Android判断当前界面是否为桌面进行查看。

另外需要特别注意的是WindowManager如果要调用.addView方法,需要为应用添加ACTION_MANAGE_OVERLAY_PERMISSION权限。否则系统会闪退,请各位格外注意。具体的添加方式这里不再多说了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值