WindowManager之凌驾所有Activity之上(自定义各种组件)

本文介绍如何利用WindowManager实现一个View凌驾于所有Activity之上,包括获取WindowManager、自定义View、设置LayoutParams属性及结合View显示的具体步骤。

要凌驾所有Activity之上一共就4步,想怎么搞就怎么搞,非常灵活.

前提是,这个东西要写在服务里,才不会被遮盖,而且需要配置权限

第一:获取一个WindowManager,获取一个属性params

mWM = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams();

第二:自己填充出一个自定义view

view = View.inflate(getApplicationContext(), R.layout.toast_ui, null);
第三:给params设置属性,也就是你想让你的view具备的额外属性

params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.type = WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
params.gravity = Gravity.LEFT + Gravity.TOP;
params.x = sp.getInt("lastX", 0);
params.y = sp.getInt("lastY", 0);
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

第四步:用WindowManager将你的view和你定义的params结合在一起显示出来

mWM.addView(view, params);






写Android程序的时候一般用WindowManager就是去获得屏幕的宽和高,来布局一些小的东西。基本上没有怎么看他的其他的接口。

这两天想写一个简单的类似于Toast的东西,自定义布局,突然发现,原来Toast的时间是不能自己定义的,只有两个固定的时间,分别是2秒和3.5秒。我的需求是自定义显示的时间,这个显然不能满足我的需求。但是它是如何做到显示一个View凌驾于现有的所有的View之上的呢?

我们Android平台是一个又一个的Activity组成的,每一个Activity有一个或者多个View构成。所以说,当我们想显示一个界面的时候,我们首先想到的是建立一个Activity,然后所有的操作在Activity里面实现,或者是一个Dialog或者Toast。这种方式固然简单,但是在有些情况下,我们要求的只是简单的显示,用Activity显然是多余,这个时候,我们如何处理呢?

原来,整个Android的窗口机制是基于一个叫做WindowManager,这个接口可以添加view到屏幕,也可以从屏幕删除view。它面向的对象一端是屏幕,另一端就是View,直接忽略我们以前的Activity或者Dialog之类的东东。其实我们的Activity或者Diolog底层的实现也是通过WindowManager,这个WindowManager是全局的,整个系统就是这个唯一的东东。它是显示View的最底层了。

WindowManager的方法很简单,基本用到的就三个addView,removeView,updateViewLayout。

而WindowManager.LayoutParams的属性就多了,非常丰富,具体请查看SDK文档。

 

写一个简单的代码:

1
2
3
4
5
WindowManager mWm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);  
Button view = newButton(this);  
view.setText("window manager test!");  
WindowManager.LayoutParams mParams = newWindowManager.LayoutParams();   
mWm.addView(view, mParams);

 

我们这个button的显示和当前的运行环境基本上是无关的,当前是什么activity或者是桌面,使用这个底层的结果给你的编程带来很大的灵活性,但是要注意,显示出来就要销毁掉,这个是必须的,销毁其实就是一个remove。

但是仅仅这么写会有个问题,就是view显示在最上层,但是后面的view无法获得焦点,当你touch的坐标超出上次view的范围时

1
2
mParams = newWindowManager.LayoutParams();  
mWm.updateViewLayout(view, mParams);

 

需要后面的view获得焦点时:

1
2
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;   
mWm.updateViewLayout(view, mParams);

 

 

 

下面是比较完整的示例代码:

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
public class myFloatView extends Activity {  
    /** Called when the activity is first created. */
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        Button bb=newButton(getApplicationContext());  
        WindowManager wm=(WindowManager)getApplicationContext().getSystemService("window");  
        WindowManager.LayoutParams wmParams = newWindowManager.LayoutParams();  
        
        /**  
         *以下都是WindowManager.LayoutParams的相关属性  
         * 具体用途请参考SDK文档  
         */
        wmParams.type=2002;  //这里是关键,你也可以试试2003  
        wmParams.format=1;  
         /**  
         *这里的flags也很关键  
         *代码实际是wmParams.flags |= FLAG_NOT_FOCUSABLE;  
         *40的由来是wmParams的默认属性(32)+ FLAG_NOT_FOCUSABLE(8)  
         */
        wmParams.flags=40;  
        wmParams.width=40;  
        wmParams.height=40;  
        wm.addView(bb, wmParams);  //创建View  
    }  
}

 

别忘了在AndroidManifest.xml中添加权限:

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

 

 PS:这里举例说明一下type的值的意思:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
        * Window type: phone.  These are non-application windows providing
        * user interaction with the phone (in particular incoming calls).
        * These windows are normally placed above all applications, but behind
        * the status bar.
        */
       public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
   
       /**
        * Window type: system window, such as low power alert. These windows
        * are always on top of application windows.
        */
public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;

 

这个FIRST_SYSTEM_WINDOW的值就是2000。2003和2002的区别就在于2003类型的View比2002类型的还要top,能显示在系统下拉状态栏之上!

WindowManager的方法很简单,基本用到的就三个addView,removeView,updateViewLayout。

而WindowManager.LayoutParams的属性就多了,非常丰富,具体请查看SDK文档。

 

 上面的代码中中使用了WindowManager.LayoutParams类,现在对其用法进行一个总结,具体如下:WindowManager.LayoutParams 是 WindowManager 接口的嵌套类;继承于 ViewGroup.LayoutParams 。
它的内容十分丰富。其实WindowManager.java的主要内容就是由这个类定义构成。下面来分析一下这个类:
定义
public static class WindowManager.LayoutParams extends ViewGroup.LayoutParams implements Parcelable

继承关系
java.lang.Object
↳android.view.ViewGroup.LayoutParams
↳android.view.WindowManager.LayoutParams

继承来的属性与常量
从 ViewManager.LayoutParams 继承来的属性:
android:layout_height
Specifies the basic height of the view.

android:layout_width
Specifies the basic width of the view.

从 ViewManager.LayoutParams继承的常量:
FILL_PARENT
WRAP_CONTENT
MATCH_PARENT
两个变量:
width
height

属性及可用的常量定义
1. public int x;
如果忽略gravity属性,那么它表示窗口的绝对X位置。
什么是gravity属性呢?简单地说,就是窗口如何停靠。
当设置了 Gravity.LEFT 或 Gravity.RIGHT 之后,x值就表示到特定边的距离。
2. public int y;
如果忽略gravity属性,那么它表示窗口的绝对Y位置。
当设置了 Gravity.TOP 或 Gravity.BOTTOM 之后,y值就表示到特定边的距离。
3. public float horizontalWeight;
public float verticalWeight;
在纵/横向上,为关联的view预留了多少扩展空间(像素)。如果是0,那么此view不能被拉伸。
其他情况下,扩展空间(像素)将被widget所均分。
4. public int type;
窗口类型。
有3种主要类型:
Applicationwindows:
取值在 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之间。
是通常的、顶层的应用程序窗口。必须将 token 设置成 activity 的 token 。
Sub_windows:
取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之间。
与顶层窗口相关联,token 必须设置为它所附着的宿主窗口的 token。
Systemwindows:
取值在 FIRST_SYSTEM_WINDOW 和 LAST_SYSTEM_WINDOW 之间。
用于特定的系统功能。它不能用于应用程序,使用时需要特殊权限。

下面定义了 type 的取值: 
应用程序窗口。
public static final int FIRST_APPLICATION_WINDOW = 1; 

所有程序窗口的“基地”窗口,其他应用程序窗口都显示在它上面。 
public static final int TYPE_BASE_APPLICATION =1;

普通应用功能程序窗口。token必须设置为Activity的token,以指出该窗口属谁。
public static final int TYPE_APPLICATION = 2;

用于应用程序启动时所显示的窗口。应用本身不要使用这种类型。
它用于让系统显示些信息,直到应用程序可以开启自己的窗口。 
public static final int TYPE_APPLICATION_STARTING = 3; 

应用程序窗口结束。
public static final int LAST_APPLICATION_WINDOW = 99;

子窗口。子窗口的Z序和坐标空间都依赖于他们的宿主窗口。
public static final int FIRST_SUB_WINDOW = 1000;

面板窗口,显示于宿主窗口上层。
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;

媒体窗口,例如视频。显示于宿主窗口下层。
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;

应用程序窗口的子面板。显示于所有面板窗口的上层。(GUI的一般规律,越“子”越靠上)
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW +2;

对话框。类似于面板窗口,绘制类似于顶层窗口,而不是宿主的子窗口。
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW +3;

媒体信息。显示在媒体层和程序窗口之间,需要实现透明(半透明)效果。(例如显示字幕)
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW +4;

子窗口结束。( End of types of sub-windows )
public static final int LAST_SUB_WINDOW = 1999;

系统窗口。非应用程序创建。
public static final int FIRST_SYSTEM_WINDOW = 2000;

状态栏。只能有一个状态栏;它位于屏幕顶端,其他窗口都位于它下方。
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;

搜索栏。只能有一个搜索栏;它位于屏幕上方。
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;

电话窗口。它用于电话交互(特别是呼入)。它置于所有应用程序之上,状态栏之下。
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;

系统提示。它总是出现在应用程序窗口之上。
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW +3;

锁屏窗口。
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW +4;

信息窗口。用于显示toast。
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW +5;

系统顶层窗口。显示在其他一切内容之上。此窗口不能获得输入焦点,否则影响锁屏。
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW +6;

电话优先,当锁屏时显示。此窗口不能获得输入焦点,否则影响锁屏。
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW +7;

系统对话框。(例如音量调节框)。
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW +8;

锁屏时显示的对话框。
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW +9;

系统内部错误提示,显示于所有内容之上。
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW +10;

内部输入法窗口,显示于普通UI之上。应用程序可重新布局以免被此窗口覆盖。
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW +11;

内部输入法对话框,显示于当前输入法窗口之上。
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW +12;

墙纸窗口。
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW +13;

状态栏的滑动面板。
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW +14;

系统窗口结束。
public static final int LAST_SYSTEM_WINDOW = 2999;

5. public int memoryType;
指出窗口所使用的内存缓冲类型。默认为 NORMAL 。

下面定义了 memoryType 的取值:
窗口缓冲位于主内存。
public static final int MEMORY_TYPE_NORMAL = 0;

窗口缓冲位于可以被DMA访问,或者硬件加速的内存区域。
public static final int MEMORY_TYPE_HARDWARE = 1;

窗口缓冲位于可被图形加速器访问的区域。
public static final int MEMORY_TYPE_GPU = 2;

窗口缓冲不拥有自己的缓冲区,不能被锁定。缓冲区由本地方法提供。
public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;

6. public int flags;
行为选项/旗标,默认为 none .
下面定义了 flags 的取值:
窗口之后的内容变暗。
public static final int FLAG_DIM_BEHIND = 0x00000002;

窗口之后的内容变模糊。
public static final int FLAG_BLUR_BEHIND = 0x00000004; 

不许获得焦点。
不能获得按键输入焦点,所以不能向它发送按键或按钮事件。那些时间将发送给它后面的可以获得焦点的窗口。此选项还会设置FLAG_NOT_TOUCH_MODAL选项。设置此选项,意味着窗口不能与软输入法进行交互,所以它的Z序独立于任何活动的输入法(换句话说,它可以全屏显示,如果需要的话,可覆盖输入法窗口)。要修改这一行为,可参考FLAG_ALT_FOCUSALBE_IM选项。
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

不接受触摸屏事件。
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;

当窗口可以获得焦点(没有设置 FLAG_NOT_FOCUSALBE 选项)时,仍然将窗口范围之外的点设备事件(鼠标、触摸屏)发送给后面的窗口处理。否则它将独占所有的点设备事件,而不管它们是不是发生在窗口范围内。
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;

如果设置了这个标志,当设备休眠时,点击触摸屏,设备将收到这个第一触摸事件。
通常第一触摸事件被系统所消耗,用户不会看到他们点击屏幕有什么反应。
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;

当此窗口为用户可见时,保持设备常开,并保持亮度不变。
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;

窗口占满整个屏幕,忽略周围的装饰边框(例如状态栏)。此窗口需考虑到装饰边框的内容。
public static final int FLAG_LAYOUT_IN_SCREEN =0x00000100;

允许窗口扩展到屏幕之外。
public static final int FLAG_LAYOUT_NO_LIMITS =0x00000200;

窗口显示时,隐藏所有的屏幕装饰(例如状态条)。使窗口占用整个显示区域。
public static final int FLAG_FULLSCREEN = 0x00000400;

此选项将覆盖FLAG_FULLSCREEN选项,并强制屏幕装饰(如状态条)弹出。
public static final int FLAG_FORCE_NOT_FULLSCREEN =0x00000800;

抖动。指 对半透明的显示方法。又称“点透”。图形处理较差的设备往往用“点透”替代Alpha混合。
public static final int FLAG_DITHER = 0x00001000;

不允许屏幕截图。
public static final int FLAG_SECURE = 0x00002000;

一种特殊模式,布局参数用于指示显示比例。
public static final int FLAG_SCALED = 0x00004000;

当屏幕有可能贴着脸时,这一选项可防止面颊对屏幕造成误操作。
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;

当请求布局时,你的窗口可能出现在状态栏的上面或下面,从而造成遮挡。当设置这一选项后,窗口管理器将确保窗口内容不会被装饰条(状态栏)盖住。
public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;

反转FLAG_NOT_FOCUSABLE选项。
如果同时设置了FLAG_NOT_FOCUSABLE选项和本选项,窗口将能够与输入法交互,允许输入法窗口覆盖;
如果FLAG_NOT_FOCUSABLE没有设置而设置了本选项,窗口不能与输入法交互,可以覆盖输入法窗口。
public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;

如果你设置了FLAG_NOT_TOUCH_MODAL,那么当触屏事件发生在窗口之外事,可以通过设置此标志接收到一个MotionEvent.ACTION_OUTSIDE事件。注意,你不会收到完整的down/move/up事件,只有第一次down事件时可以收到ACTION_OUTSIDE。
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;

当屏幕锁定时,窗口可以被看到。这使得应用程序窗口优先于锁屏界面。可配合FLAG_KEEP_SCREEN_ON选项点亮屏幕并直接显示在锁屏界面之前。可使用FLAG_DISMISS_KEYGUARD选项直接解除非加锁的锁屏状态。此选项只用于最顶层的全屏幕窗口。
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;

请求系统墙纸显示在你的窗口后面。窗口必须是半透明的。
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;

窗口一旦显示出来,系统将点亮屏幕,正如用户唤醒设备那样。
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;

解除锁屏。只有锁屏界面不是加密的才能解锁。如果锁屏界面是加密的,那么用户解锁之后才能看到此窗口,除非设置了FLAG_SHOW_WHEN_LOCKED选项。
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;

锁屏界面淡出时,继续运行它的动画。
public static final int FLAG_KEEP_SURFACE_WHILE_ANIMATING =0x10000000;

以原始尺寸显示窗口。用于在兼容模式下运行程序。
public static final int FLAG_COMPATIBLE_WINDOW = 0x20000000;

用于系统对话框。设置此选项的窗口将无条件获得焦点。
public static final int FLAG_SYSTEM_ERROR = 0x40000000;

7. public int softInputMode;
软输入法模式选项:
以下选项与 softInputMode 有关:
软输入区域是否可见。
public static final int SOFT_INPUT_MASK_STATE = 0x0f;

未指定状态。
public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;

不要修改软输入法区域的状态。
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;

隐藏输入法区域(当用户进入窗口时)。
public static final int SOFT_INPUT_STATE_HIDDEN = 2;

当窗口获得焦点时,隐藏输入法区域。
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;

显示输入法区域(当用户进入窗口时)。
public static final int SOFT_INPUT_STATE_VISIBLE = 4;

当窗口获得焦点时,显示输入法区域。
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;

窗口应当主动调整,以适应软输入窗口。
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;

未指定状态,系统将根据窗口内容尝试选择一个输入法样式。
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;

当输入法显示时,允许窗口重新计算尺寸,使内容不被输入法所覆盖。
不可与SOFT_INPUT_ADJUSP_PAN混合使用,如果两个都没有设置,系统将根据窗口内容自动设置一个选项。
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;

输入法显示时平移窗口。它不需要处理尺寸变化,框架能够移动窗口以确保输入焦点可见。
不可与SOFT_INPUT_ADJUST_RESIZE混合使用;如果两个都没设置,系统将根据窗口内容自动设置一个选项。
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;

当用户转至此窗口时,由系统自动设置,所以你不要设置它。
当窗口显示之后该标志自动清除。
public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;

8. public int gravity;
gravity 属性。什么是gravity属性呢?简单地说,就是窗口如何停靠。

9. public float horizontalMargin;
水平边距,容器与widget之间的距离,占容器宽度的百分率。

10. public float verticalMargin;
纵向边距。

11. public int format;
期望的位图格式。默认为不透明。参考android.graphics.PixelFormat。

12. public int windowAnimations;
窗口所使用的动画设置。它必须是一个系统资源而不是应用程序资源,因为窗口管理器不能访问应用程序。

13. public float alpha = 1.0f;
整个窗口的半透明值,1.0表示不透明,0.0表示全透明。

14. public float dimAmount = 1.0f;
当FLAG_DIM_BEHIND设置后生效。该变量指示后面的窗口变暗的程度。
1.0表示完全不透明,0.0表示没有变暗。

15. public float screenBrightness = -1.0f;
用来覆盖用户设置的屏幕亮度。表示应用用户设置的屏幕亮度。
从0到1调整亮度从暗到最亮发生变化。

16. public IBinder token = null;
窗口的标示符。( Identifier for this window. This will usually be filled in for you. )

17. public String packageName = null;
此窗口所在的包名。

18. public int screenOrientation =ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
屏幕方向,参见android.content.pm.ActivityInfo#screenOrientation。

19. 在兼容模式下,备份/恢复参数所使用的内部缓冲区。
public int[] mCompatibilityParamsBackup = null;

<think> 首先,用户的问题是关于 Android 开发中的悬浮窗(floating window)放置在 singleTask Activity 之上的实现方法。用户特别提到了“singleTask Activity”,所以我需要回忆一下 Android 的启动模式。 从系统指令中,我必须遵守格式规则:行内数学表达式用 `$...$`,独立公式用 `$$...$$`,但这个问题中没有数学相关内容,所以可能不需要。所有回答必须是中文。最后生成相关问题。 用户提供了几个引用: - 引用[1]: 描述了 standard、singleTop 启动模式,但没有提及 singleTask。 - 引用[2]: 解释 Activity 的基本概念。 - 引用[3]: 讨论了 FLAG_ACTIVITY_NEW_TASK,类似于 singleTask。 - 引用[4]: 关于 Activity 的生命周期方法。 - 引用[5]: 描述了 singleTask 模式(栈内复用模式)。 用户的问题核心是:如何将悬浮窗放置在 singleTask Activity 之上。我需要理解悬浮窗在 Android 中是如何实现的。 在 Android 中,悬浮窗通常使用 WindowManager 来创建和管理,特别是通过添加视图到系统中,使用 TYPE_APPLICATION_OVERLAY 类型(在 Android O 及以后)。 关键点是,悬浮窗是系统级别的窗口,它们独立于 Activity 栈。所以,悬浮窗应该能够显示在任何 Activity 之上,包括 singleTask Activity,因为它是全局的。 用户说“放置在 singleTask Activity 之上”,意思是悬浮窗显示在 singleTask Activity 的上面,而不是被 Activity 覆盖。 在 Android 中,悬浮窗的层级是由其窗口类型决定的。例如,TYPE_APPLICATION_OVERLAY 会显示在所有应用程序窗口之上,包括任何 Activity。 所以,实现方法应该与 Activity 的启动模式无关,因为悬浮窗不是 Activity 的一部分;它是独立视图。 回想一下: - singleTask Activity 确保在任务栈中只有一个实例,当启动时,如果已存在,会复用实例并清除栈顶的其他 Activity。 - 悬浮窗是通过 WindowManager.addView() 添加的视图,可以设置其窗口参数,如 type、flags 等来控制其位置和层级。 因此,要将悬浮窗放置在 singleTask Activity 之上,关键是正确设置悬浮窗的窗口参数,使之具有较高的类型,比如 FLAG_NOT_FOCUSABLE 或使用 OVERLAY 类型。 具体步骤: 1. 创建一个视图(例如,一个自定义 View)。 2. 使用 WindowManager 添加这个视图。 3. 设置 WindowManager.LayoutParams,指定 type 为 TYPE_APPLICATION_OVERLAY(对于 Android O+)或 TYPE_PHONE(对于旧版本,但需要权限)。 4. 确保悬浮窗有适当的层级设置,使其在所有应用内容之上。 对于 singleTask Activity,由于悬浮窗是独立的,它应该自然地显示在所有 Activity 之上,包括 singleTask,前提是悬浮窗的层级设置正确。 用户提到“放置在 singleTask Activity 之上”,这可能意味着当 singleTask Activity 启动时,悬浮窗不被清除或保持在上层。由于悬浮窗是全局的,它不应该被 Activity 的生命周期影响。 在实现中,注意权限:对于悬浮窗,需要请求 SYSTEM_ALERT_WINDOW 权限。 现在,结构化我的回答: - 首先,解释悬浮窗的基本实现。 - 然后,说明如何设置窗口参数以确保它在所有内容之上。 - 给出简单代码示例。 最后,生成相关问题。 确保回答是中文的。 参考用户提供的引用: - 引用[5] 直接描述了 singleTask 模式:栈内复用模式,当新建 Activity 时,如果实例已存在,会复用并清除栈顶 Activity。 - 引用[3] 讨论了 FLAG_ACTIVITY_NEW_TASK,类似 singleTask。 但悬浮窗不是 Activity,所以引用可能不完全相关。不过,我可以基于此解释。 核心实现: - 创建一个 Service 来管理悬浮窗,因为悬浮窗通常需要后台运行。 - 在 Service 中,使用 WindowManager 添加视图。 代码示例: ```java // 在 Service 中 WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, // 对于 Android O 及以上 Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); View floatView = LayoutInflater.from(this).inflate(R.layout.float_layout, null); windowManager.addView(floatView, params); ``` 然后,对于 singleTask Activity,不需要特别处理,因为悬浮窗已经是全局的。 用户说“放置在之上”,可能意味着当 singleTask Activity 启动时,它可能会清除栈,但悬浮窗不受影响。 所以,在回答中,我应该说悬浮窗的实现与 Activity 启动模式无关,只需确保正确设置窗口类型。 最终,回答结构: 1. 解释悬浮窗的基本概念和实现原理。 2. 给出具体实现方法,包括代码示例。 3. 指出为什么它能在 singleTask Activity 之上。 生成相关问题。 </think> ### Android 悬浮窗放置在 singleTask Activity 之上的实现方法 在 Android 中,悬浮窗(Floating Window)是一种全局视图组件,它独立于 Activity 任务栈,通常通过 `WindowManager` 实现。由于 singleTask Activity 具有栈内复用特性(当启动时,如果任务栈已存在该实例,会复用实例并清除栈顶其他 Activity,调用 `onNewIntent()`)[^5],悬浮窗需要设置正确的窗口类型(如 `TYPE_APPLICATION_OVERLAY`)才能确保显示在所有 Activity 之上,包括 singleTask 模式。以下是具体实现步骤和代码示例。 #### 实现步骤 1. **添加权限**:在 `AndroidManifest.xml` 中声明悬浮窗权限(Android 8.0+ 需动态请求)。 ```xml <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> ``` 2. **创建悬浮窗视图**:设计悬浮窗的布局(如 XML 文件),并在代码中实例化。 3. **配置 WindowManager.LayoutParams**:设置窗口类型为 `TYPE_APPLICATION_OVERLAY`(Android O 及以上)或 `TYPE_PHONE`(旧版),并指定层级标志(如 `FLAG_NOT_FOCUSABLE`),确保悬浮窗位于所有 Activity 之上。 4. **使用 WindowManager 添加视图**:通过 `WindowManager.addView()` 添加悬浮窗视图。 5. **处理 singleTask Activity 场景**:悬浮窗是系统级视图,不受 Activity 生命周期影响。当 singleTask Activity 启动时(例如复用实例并清除栈顶 Activity),悬浮窗依然保持显示状态,因为其窗口层级高于应用界面。 #### 代码示例 以下是一个简单实现,展示如何创建悬浮窗并将其置于 singleTask Activity 之上。建议在 `Service` 中管理悬浮窗以确保后台运行。 ```java // 悬浮窗服务类(FloatWindowService.java) public class FloatWindowService extends Service { private WindowManager windowManager; private View floatView; @Override public void onCreate() { super.onCreate(); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); // 1. 初始化悬浮窗视图 floatView = LayoutInflater.from(this).inflate(R.layout.float_layout, null); // 2. 配置窗口参数 WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, // 根据 SDK 版本选择类型(Android O+ 用 OVERLAY,旧版用 PHONE) Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, // 避免获取焦点,不影响 Activity 交互 PixelFormat.TRANSLUCENT ); // 3. 设置悬浮窗位置(例如屏幕右上角) params.gravity = Gravity.TOP | Gravity.END; params.x = 0; params.y = 100; // 4. 添加视图到 WindowManager windowManager.addView(floatView, params); } @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); if (floatView != null) { windowManager.removeView(floatView); // 移除悬浮窗 } } } ``` #### 关键说明 - **为什么能显示在 singleTask Activity 之上?** 悬浮窗使用 `TYPE_APPLICATION_OVERLAY` 类型,其层级(Z-order)高于所有应用窗口(包括 singleTask Activity)。Android 窗口管理器会优先渲染悬浮窗,确保它始终位于顶部,不受 Activity 栈变化影响[^3]。 - **singleTask Activity 的影响**: 当启动 singleTask Activity 时,系统可能清除任务栈顶的其他 Activity,但悬浮窗 **不会** 被销毁或隐藏(除非主动移除)。悬浮窗独立于 Activity 生命周期,因此复用 singleTask 实例时(如调用 `onNewIntent()`),悬浮窗保持可见[^5]。 - **注意事项**: - **动态权限**:在 Android 6.0+ 上,需运行时请求 `SYSTEM_ALERT_WINDOW` 权限(使用 `Settings.ACTION_MANAGE_OVERLAY_PERMISSION`)。 - **性能优化**:避免在悬浮窗中执行耗时操作,防止阻塞主线程。 - **兼容性**:测试不同 Android 版本(尤其是 O 及以上),确保 `TYPE_APPLICATION_OVERLAY` 正确生效。 通过以上方法,悬浮窗能在 singleTask Activity 或其他任何模式下稳定显示。实际测试中,悬浮窗应出现在所有界面之上,类似系统级的通知或弹窗[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值