Android悬浮窗实现 使用WindowManager

本文转载自: http://blog.csdn.net/stevenhu_223/article/details/8504058

悬浮窗口的实现涉及到WindowManager(基于4.0源码分析),它是一个接口,实现类有WindowManagerImpl,CompatModeWrapper(WindowManagerImpl的内部类),LocalWindowManager(Window的内部类),它们之间的关系如下图的类图:

WindowManagerImpl:

1.是WindowManager的实现类,windowmanager的大部分操作都在这里实现,但是并不会直接调用,而是作为LocalWindowManager和WindowManagerImpl.CompatModeWrapper的成员变量来使用。

2.在WindowManagerImpl中有3个数组View[],ViewRoot[],WindowManager.LayoutParams[],分别用来保存每个图层的数据。

3.WindowManagerImpl最重要的作用就是用来管理View,LayoutParams, 以及ViewRoot这三者的对应关系。

LocalWindowManager:

在源码的Activity类中,有一个重要的成员变量mWindow(它的实现类为PhoneWindow),同时也有一个成员变量mWindowManager(跟踪源码可知它是一个LocalWindowManager),而在PhoneWindow中同时也有和Activity相同名字的mWindowManager成员变量。而且Activity中的mWindowManager是通过Window类中的setWindowManager函数初始化获取的。

所以,在Activity中的LocalWindowManager的生命周期是小于Activity的生命周期的,而且在ActivityThread每创建一个Activity时都有该Activity对应的一个属于它的LocalWindowManager。

对LocalWindowManager的小结:

1.该类是Window的内部类,父类为CompatModeWrapper,同样都是实现WindowManager接口。

2.每个Activity中都有一个mWindowManager成员变量,Window类中 也有相应的同名字的该成员变量。该变量是通过调用Window的setWindowManager方法初始化得到的,实际上是一个LocalWindowManger对象。

3.也就说,每生成的一个Activity里都会构造一个其相应LocalWindowManger来管理该Activity承载的图层。(该对象可以通过Activity.getWindowManager或getWindow().getWindowManager获取)

4.LocalWindowMangers 的生命周期小于Activity的生命周期,(因为mWindowManager是Window的成员变量,而mWindow又是Activity的成员变量),所以,如果我们在一个LocalwindowManager中手动添加了其他的图层, 在Activity的finish执行之前, 应该先调用LocalwindowManager的removeView, 否则会抛出异常。

CompatModeWrapper:

该类就是实现悬浮窗口的重要类了。

跟踪源码可知:

1.CompatModeWrapper相当于是一个壳,而真正实现大部分功能的是它里面的成员变量mWindowManager(WindowManagerImpl类)。

2.该对象可以通过getApplication().getSystemService(Context.WINDOW_SERVICE)得到。(注:如果是通过activity.getSystemService(Context.WINDOW_SERVICE)得到的只是属于Activity的LocalWindowManager)。

3.这个对象的创建是在每个进程开始的时候, 通过ContextImpl中的静态代码块创建的, 它使用了单例模式, 保证每个application只有一个。

4.通过该类可以实现创建添加悬浮窗口,也就是说,在退出当前Activity时,通过该类创建的视图还是可见的,它是属于整个应用进程的视图,存活在进程中,不受Activity的生命周期影响。

ok,在通过上面对WindowManager接口的实现类做一些简要的介绍后,接下来就动手编写实现悬浮窗口的App。既然我们知道可以通过getApplication().getSystemService(Context.WINDOW_SERVICE)得到CompatModeWrapper,然后实现应用添加悬浮窗口视图。那么,具体的实现操作可以在Activity或者Service中(这两者都是可以创建存活在应用进程中的Android重要组件)实现。

下面的App程序代码实现通过主Activity的启动按钮,启动一个Service,然后在Service中创建添加悬浮窗口:

要获取CompatModeWrapper,首先得在应用程序的AndroidManifest.xml文件中添加权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

MainActivity的代码如下:

public class MainActivity extends Activity
{

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
		//获取启动按钮
        Button start = (Button)findViewById(R.id.start_id);
        //获取移除按钮
        Button remove = (Button)findViewById(R.id.remove_id);
        //绑定监听
        start.setOnClickListener(new OnClickListener()
        {

			@Override
			public void onClick(View v)
			{
				// TODO Auto-generated method stub
				Intent intent = new Intent(MainActivity.this, FxService.class);
				//启动FxService
				startService(intent);
				finish();
			}
		});

        remove.setOnClickListener(new OnClickListener()
        {

			@Override
			public void onClick(View v)
			{
				//uninstallApp("com.phicomm.hu");
				Intent intent = new Intent(MainActivity.this, FxService.class);
				//终止FxService
				stopService(intent);
			}
		});

    }
}

  

FxService的代码如下:

package com.phicomm.hu;

import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;

public class FxService extends Service
{

	//定义浮动窗口布局
    LinearLayout mFloatLayout;
    WindowManager.LayoutParams wmParams;
    //创建浮动窗口设置布局参数的对象
	WindowManager mWindowManager;

	Button mFloatView;

	private static final String TAG = "FxService";

	@Override
	public void onCreate()
	{
		// TODO Auto-generated method stub
		super.onCreate();
		Log.i(TAG, "oncreat");
		createFloatView();
	}

	@Override
	public IBinder onBind(Intent intent)
	{
		// TODO Auto-generated method stub
		return null;
	}

	private void createFloatView()
	{
		wmParams = new WindowManager.LayoutParams();
		//获取的是WindowManagerImpl.CompatModeWrapper
		mWindowManager = (WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);
		Log.i(TAG, "mWindowManager--->" + mWindowManager);
		//设置window type
		wmParams.type = LayoutParams.TYPE_PHONE;
		//设置图片格式,效果为背景透明
        wmParams.format = PixelFormat.RGBA_8888;
        //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
        wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
        //调整悬浮窗显示的停靠位置为左侧置顶
        wmParams.gravity = Gravity.LEFT | Gravity.TOP;
        // 以屏幕左上角为原点,设置x、y初始值,相对于gravity
        wmParams.x = 0;
        wmParams.y = 0;

        //设置悬浮窗口长宽数据
        wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

		 /*// 设置悬浮窗口长宽数据
        wmParams.width = 200;
        wmParams.height = 80;*/

        LayoutInflater inflater = LayoutInflater.from(getApplication());
        //获取浮动窗口视图所在布局
        mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);
        //添加mFloatLayout
        mWindowManager.addView(mFloatLayout, wmParams);
        //浮动窗口按钮
        mFloatView = (Button)mFloatLayout.findViewById(R.id.float_id);

        mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0,
				View.MeasureSpec.UNSPECIFIED), View.MeasureSpec
				.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
        Log.i(TAG, "Width/2--->" + mFloatView.getMeasuredWidth()/2);
        Log.i(TAG, "Height/2--->" + mFloatView.getMeasuredHeight()/2);
        //设置监听浮动窗口的触摸移动
        mFloatView.setOnTouchListener(new OnTouchListener()
        {

			@Override
			public boolean onTouch(View v, MotionEvent event)
			{
				// TODO Auto-generated method stub
				//getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标
				wmParams.x = (int) event.getRawX() - mFloatView.getMeasuredWidth()/2;
				Log.i(TAG, "RawX" + event.getRawX());
				Log.i(TAG, "X" + event.getX());
				//减25为状态栏的高度
	            wmParams.y = (int) event.getRawY() - mFloatView.getMeasuredHeight()/2 - 25;
	            Log.i(TAG, "RawY" + event.getRawY());
	            Log.i(TAG, "Y" + event.getY());
	             //刷新
	            mWindowManager.updateViewLayout(mFloatLayout, wmParams);
				return false;  //此处必须返回false,否则OnClickListener获取不到监听
			}
		});	

        mFloatView.setOnClickListener(new OnClickListener()
        {

			@Override
			public void onClick(View v)
			{
				// TODO Auto-generated method stub
				Toast.makeText(FxService.this, "onClick", Toast.LENGTH_SHORT).show();
			}
		});
	}

	@Override
	public void onDestroy()
	{
		// TODO Auto-generated method stub
		super.onDestroy();
		if(mFloatLayout != null)
		{
			//移除悬浮窗口
			mWindowManager.removeView(mFloatLayout);
		}
	}

}

  

悬浮窗口的布局文件为R.layout.float_layout,所以,如果我们想设计一个非常美观的悬浮窗口,可以在该布局文件里编写。当然,也可以使用自定义View来设计(哈哈,少年们,在此基础上发挥想象吧)。

上面代码的效果图如下:左边为启动界面。点击“启动悬浮窗口”按钮,会启动后台service创建悬浮窗口,同时finish当前Activity,这样一个悬浮窗口就创建出来了,该窗口可实现任意位置移动,且可点击监听创建Toast提示(当然,也可以启动一个Activity)。若要移除已创建的窗口,可点击“移除悬浮窗口按钮”,或者强制禁止该应用进程。

      

同样的,在一个Activity里绘制悬浮视图,不过下面的代码主要还是验证区分LocalWindowManger和CompatModeWrapper添加的视图。

LocalWindowManger可通过activity.getSystemService(Context.WINDOW_SERVICE)或getWindow().getWindowManager获取。当我们通过LocalWindowManger添加视图时,退出Activity,添加的视图也会随之消失。

验证代码如下:

package com.phicomm.hu;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;

public class FloatWindowTest extends Activity
{
    /** Called when the activity is first created. */

	private static final String TAG = "FloatWindowTest";
	WindowManager mWindowManager;
	WindowManager.LayoutParams wmParams;
	LinearLayout mFloatLayout;
	Button mFloatView;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        //createFloatView();
        setContentView(R.layout.main);

        Button start = (Button)findViewById(R.id.start);
        Button stop = (Button)findViewById(R.id.stop);

        start.setOnClickListener(new OnClickListener()
        {

			@Override
			public void onClick(View v)
			{
				// TODO Auto-generated method stub
				createFloatView();
				//finish();
				//handle.post(r);
			}
		});

        stop.setOnClickListener(new OnClickListener()
        {

			@Override
			public void onClick(View v)
			{
				// TODO Auto-generated method stub
				if(mFloatLayout != null)
				{
					mWindowManager.removeView(mFloatLayout);
					finish();
				}
		}
		});

    }

    private void createFloatView()
    {
    	//获取LayoutParams对象
        wmParams = new WindowManager.LayoutParams();

        //获取的是LocalWindowManager对象
        mWindowManager = this.getWindowManager();
        Log.i(TAG, "mWindowManager1--->" + this.getWindowManager());
        //mWindowManager = getWindow().getWindowManager();
        Log.i(TAG, "mWindowManager2--->" + getWindow().getWindowManager());

        //获取的是CompatModeWrapper对象
        //mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
        Log.i(TAG, "mWindowManager3--->" + mWindowManager);
        wmParams.type = LayoutParams.TYPE_PHONE;
        wmParams.format = PixelFormat.RGBA_8888;;
        wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
        wmParams.gravity = Gravity.LEFT | Gravity.TOP;
        wmParams.x = 0;
        wmParams.y = 0;
        wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

        LayoutInflater inflater = this.getLayoutInflater();//LayoutInflater.from(getApplication());

        mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);
        mWindowManager.addView(mFloatLayout, wmParams);
        //setContentView(R.layout.main);
        mFloatView = (Button)mFloatLayout.findViewById(R.id.float_id);

        Log.i(TAG, "mFloatView" + mFloatView);
        Log.i(TAG, "mFloatView--parent-->" + mFloatView.getParent());
        Log.i(TAG, "mFloatView--parent--parent-->" + mFloatView.getParent().getParent());
        //绑定触摸移动监听
        mFloatView.setOnTouchListener(new OnTouchListener()
        {

			@Override
			public boolean onTouch(View v, MotionEvent event)
			{
				// TODO Auto-generated method stub
				wmParams.x = (int)event.getRawX() - mFloatLayout.getWidth()/2;
				//25为状态栏高度
				wmParams.y = (int)event.getRawY() - mFloatLayout.getHeight()/2 - 40;
				mWindowManager.updateViewLayout(mFloatLayout, wmParams);
				return false;
			}
		});

        //绑定点击监听
        mFloatView.setOnClickListener(new OnClickListener()
        {

			@Override
			public void onClick(View v)
			{
				// TODO Auto-generated method stub
				Intent intent = new Intent(FloatWindowTest.this, ResultActivity.class);
				startActivity(intent);
			}
		});

    }
}

  

将上面的代码相关注释部分取消,然后运行代码查看Log信息,那么就可以知道问题所在了(每一个Activity对应一个LocalWindowManger,每一个App对应一个CompatModeWrapper),所以要实现在App所在进程中运行的悬浮窗口,当然是得要获取CompatModeWrapper,而不是LocalWindowManger。

本文相关的完整代码下载链接:http://download.csdn.net/detail/stevenhu_223/4996970

另:

悬浮窗如何覆盖到任务栏之上呢?

flags里加上这两个: LayoutParams.FLAG_FULLSCREEN
和LayoutParams.FLAG_LAYOUT_IN_SCREEN
。然后type用 LayoutParams.TYPE_SYSTEM_ERROR。就可以了

时间: 2024-12-28 02:48:39

Android悬浮窗实现 使用WindowManager的相关文章

Android 悬浮窗、悬浮球开发

原文:Android 悬浮窗.悬浮球开发 1.权限管理 直接看我另外一篇博客吧,传送门: https://my.oschina.net/u/1462828/blog/1933162 2.Base类BaseSuspend import android.content.Context; import android.graphics.PixelFormat; import android.os.Build; import android.view.Gravity; import android.vi

Android 悬浮窗权限校验

原文:Android 悬浮窗权限校验 悬浮窗权限: <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> 权限检验和请求: //检查是否已经授予权限,大于6.0的系统适用,小于6.0系统默认打开,无需理会 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M&&!Settings.canDrawOverlays(

Android 悬浮窗

iPhone有个很好用的白色圆点,今天就来研究下Android中的悬浮框,这里主要是实现一个快捷键的功能,当然也可以在悬浮框中做想做的事! 悬浮窗的实现主要是通过WindowManager实现,当然WindowManager只是一个接口,想了解源码的同志们可以去看WindowManagerImpl,悬浮框主要是通过WindowManager中的addView,updateView,removeView实现 WindowManager.LayoutParams这个类用于提供悬浮窗所需的参数 Win

Android 悬浮窗各机型各系统适配大全

这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时,感兴趣或者找到其他机型适配方法的请留言告诉我,或者加群544645972一起交流一下,非常感谢~ 相关权限请看我的另一篇博客:android permission权限与安全机制解析(下),或者关于权限的案例使用:android WindowManager解析与骗取QQ密码案例分析. 转载请注明出处

android悬浮窗--获取内存显示当前内存使用量

原文地址:http://www.android100.org/html/201306/20/3224.html 运行效果: 其中, 这一块就是悬浮窗,可以随意拖动,动态显示当前内存使用量. 下面看一下代码是如何实现的: 悬浮窗的实现是用了一个service,为什么要用service呢?了解service特点的大体就会明白.下面看一下: public class FloatService extends Service { WindowManager wm = null; WindowManage

Android 悬浮窗的实现源码

转载请标明出处:http://blog.csdn.net/pcaxb/article/details/48048529 悬浮窗的实现用到了一个Service.自定义View.Activity 效果图 悬浮窗的实现用到的权限: <!-- 仿360浮动插件需要的权限 --> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission and

Android悬浮窗原理解析(Window)[源码]

悬浮窗,在大多数应用中还是很少见的,目前我们接触到的悬浮窗,差不多都是一些系统级的应用软件,例如:360安全卫士,腾讯手机管家等:在某些服务行业如金融,餐饮等,也会在应用中添加悬浮窗,例如:美团的偷红包,博闻金融快捷联系等.但两种悬浮窗还是有区别的: 系统悬浮窗:所有界面都会展示,包括主屏.锁屏 应用悬浮窗:只在应用Activity中展示. 一.窗口Window 在了解悬浮窗之前,首先我们需要认识一下Android窗口Window.Android Framework将窗口分为三个类型: 应用窗口

关于Android悬浮窗要获取按键响应的问题

要在Android中实现顶层的窗口弹出,一般都会用WindowsManager来实现,但是几乎所有的网站资源都是说弹出的悬浮窗不用接受任何按键响应. 而问题就是,我们有时候需要他响应按键,比如电视上的android,我们要它响应遥控器上的音量按键等等之类的.这时就必须要对添加的View进行LayoutParams的相关设置了. 主要的代码就两个地方. 第一,添加的view不可以设置layoutParams.flags=LayoutParams.FLAG_NOT_FOCUSABLE;//否则就完全

实现Android悬浮窗仅在本APP的所有界面显示

> 用WindowManager实现的悬浮窗为系统的窗口,会在app后台时仍然显示在界面上. 在实现需求的悬浮窗功能之后: (一) 把 WindowManager对象的addView()方法中添加的myView 放在自定义的 application类中申明(static),并添加get() set()方法. (二)新建一个BaseActivity,在onCreate()通过application获得myView,并在其生命周期方法onResume()中添加myView.setVisibility