一、悬浮窗基础

悬浮窗是属于Android系统的一种浮动窗口,可以在其他应用程序的上层显示,可以随意拖动、缩放、关闭等操作,常用于提醒、通知、广告等。本节将着重介绍悬浮窗的基本概念和使用方法。

1.1 悬浮窗的基本构成

在Android系统中,每个窗口都对应一个Window对象,而悬浮窗就是一种特殊的Window,通常采用从系统层面抽象出的ViewSystem中的PopupWindow来实现。

其中,PopupWindow是继承自具有运动能力的WindowManager.LayoutParams的一个类,这也意味着我们可以随意对其进行位置、大小、显示方式等操作。因此,我们可以使用PopupWindow实现一个不影响其他应用可随意操作且不需要Activity跳转的自定义悬浮窗。

1.2 悬浮窗的实现方法

实现一个悬浮窗分为以下几个步骤:

(1)在AndroidManifest.xml中声明悬浮窗权限

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

(2)在需要显示悬浮窗的Activity或Service里面创建WindowManager和PopupWindow,设置其显示位置、大小和内容等属性

//创建布局
View layout = View.inflate(this, R.layout.float_window, null);
//创建管理器
WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);
//创建悬浮窗
PopupWindow mPopupWindow = new PopupWindow(layout,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT);
//设置显示位置
mPopupWindow.showAtLocation(parentView, Gravity.LEFT | Gravity.TOP, x, y);
//设置可点击和可获取焦点
mPopupWindow.setTouchable(true);
mPopupWindow.setFocusable(true);
//设置背景色
mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

(3)设置悬浮窗布局内控件的点击事件、拖拽监听事件等

layout.findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(FloatWindowActivity.this, "You clicked button1", Toast.LENGTH_SHORT).show();
    }
});

mPopupWindow.setOnTouchListener(new View.OnTouchListener() {
    int lastX, lastY;
    int paramX, paramY;

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        //获取当前触摸点相对于屏幕的坐标
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                paramX = mPopupWindow.getLayoutParams().x;
                paramY = mPopupWindow.getLayoutParams().y;
                break;
            case MotionEvent.ACTION_MOVE:
                int dx = x - lastX;
                int dy = y - lastY;
                mPopupWindow.update(paramX + dx, paramY + dy, -1, -1);
                break;
        }
        return false;
    }
});

二、悬浮窗应用场景与实现技巧

悬浮窗可以提供多种便利的功能和交互操作,因此在实际应用中有广泛的应用场景,常见的有:

2.1 悬浮窗提醒

悬浮窗可以在用户浏览其他应用时,提醒用户即时更新消息,如即时通讯软件的新消息提醒。

2.2 悬浮窗广告

悬浮窗可以向用户展示信息,可以作为一种广告展示方式出现在App中。

2.3 悬浮窗控制

悬浮窗可以设置在屏幕顶部或底部,方便用户随时控制应用的运行和停止,如音乐播放器的播放控制。

2.4 悬浮窗显示技巧

在实际开发过程中,可以通过以下方式优化悬浮窗的用户体验:

(1)悬浮窗自适应

在不同屏幕分辨率和设备角度下,悬浮窗需要能够自适应大小或宽高比,提升用户体验;

(2)悬浮窗延时

为避免误操作和用户视觉干扰,可以设置延时显示或延时关闭功能;

(3)悬浮窗透明度

悬浮窗可以设置透明度或背景色,适配不同的主题和App风格;

(4)悬浮窗关闭方法

悬浮窗需要设置方便关闭的方法,例如双击、四指缩小等。

三、悬浮窗实例

下面给出一个简单的悬浮窗实例:

首先,在AndroidManifest.xml中添加悬浮窗权限声明:

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

然后,在MainActivity中添加以下代码:

private void showFloatWindow() {
    //创建布局
    View layout = View.inflate(this, R.layout.float_window, null);
    //创建管理器
    WindowManager wm = (WindowManager) this.getSystemService(Context.WINDOW_SERVICE);

    //获取屏幕宽高以及状态栏高度
    DisplayMetrics dm = new DisplayMetrics();
    wm.getDefaultDisplay().getMetrics(dm);
    int screenWidth = dm.widthPixels;
    int screenHeight = dm.heightPixels;
    int statusBarHeight = getStatusBarHeight(this);

    //创建悬浮窗
    PopupWindow mPopupWindow = new PopupWindow(layout,
                    (int) (screenWidth * 0.8),
                    (int) (screenHeight * 0.8));
    //设置显示位置
    mPopupWindow.showAtLocation(findViewById(R.id.main_view), Gravity.TOP,
                    (int) (screenWidth * 0.1),
                    (int) (statusBarHeight + screenHeight * 0.2));
    //设置可点击和可获取焦点
    mPopupWindow.setTouchable(true);
    mPopupWindow.setFocusable(true);
    //设置背景色
    mPopupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));

    //设置关闭方法
    layout.findViewById(R.id.close).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mPopupWindow.dismiss();
        }
    });
}

//获取状态栏高度
public int getStatusBarHeight(Context context) {
    int statusBarHeight = 0;
    Resources res = context.getResources();
    int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        statusBarHeight = res.getDimensionPixelSize(resourceId);
    }
    return statusBarHeight;
}

layout.xml布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:background="#66000000">

    <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textColor="#ffffff"
            android:layout_marginTop="48dp"
            android:textSize="20sp"
            android:text="这是一条悬浮窗信息"/>
            
    <Button
            android:id="@+id/close"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:background="@mipmap/ic_launcher"
            android:layout_alignParentRight="true"
            android:layout_marginTop="8dp"
            android:layout_marginRight="8dp"/>
            
</RelativeLayout>

运行效果如下图所示: