xsmile
You can fly higher
xsmile's Blog
Android中可自由移动悬浮窗口的实现

第二届 Google 暑期大学生博客分享大赛 – 2011 Android 成长篇

本文为参加Google暑期大学生博客分享大赛特别撰写。


—————————————————————-

大家对悬浮窗概念不会陌生,相信每台电脑桌面的右上角都会有这么一个东西,它总是出现在所有页面的顶端(Top Show)。但在Android平台中如何实现这样的效果呢?先来看一看效果图。

https://www.xsmile.top/xsmiletop/wp-content/uploads/2011/07/悬浮窗.jpg

看见在Google搜索框上面的那个Icon图片了嘛。下面我就来详细介绍一下在Android平台下悬浮窗口的实现,并让它能够随手指的触摸而移动。

一、实现原理及移动思路

调用WindowManager,并设置WindowManager.LayoutParams的相关属性,通过WindowManager的addView方法创建View,这样产生出来的View根据WindowManager.LayoutParams属性不同,效果也就不同了。比如创建系统顶级窗口,实现悬浮窗口效果!然后通过覆写悬浮View中onTouchEvent方法来改变windowMananager.LayoutParams中x和y的值来实现自由移动悬浮窗口。

二、示例代码

先来看一看悬浮View的代码,这里用一个ImageView作为演示

public class MyFloatView extends ImageView {
    private float mTouchStartX;
    private float mTouchStartY;
    private float x;
    private float y;
    
    private WindowManager wm=(WindowManager)getContext().getApplicationContext().getSystemService("window");
    //此wmParams变量为获取的全局变量,用以保存悬浮窗口的属性
    private WindowManager.LayoutParams wmParams = ((MyApplication)getContext().getApplicationContext()).getMywmParams();

	public MyFloatView(Context context) {
		super(context);		
		// TODO Auto-generated constructor stub
	}
	
	 @Override
	 public boolean onTouchEvent(MotionEvent event) {
	     //获取相对屏幕的坐标,即以屏幕左上角为原点		 
	     x = event.getRawX();   
	     y = event.getRawY()-25;   //25是系统状态栏的高度
	     Log.i("currP", "currX"+x+"====currY"+y);
	     switch (event.getAction()) {
	        case MotionEvent.ACTION_DOWN:    //捕获手指触摸按下动作
	            //获取相对View的坐标,即以此View左上角为原点
	            mTouchStartX =  event.getX();  
                    mTouchStartY =  event.getY();                
	            Log.i("startP", "startX"+mTouchStartX+"====startY"+mTouchStartY);
	            break;

	        case MotionEvent.ACTION_MOVE:	//捕获手指触摸移动动作            
	            updateViewPosition();
	            break;

	        case MotionEvent.ACTION_UP:    //捕获手指触摸离开动作
	            updateViewPosition();
	            mTouchStartX=mTouchStartY=0;
	            break;
	        }
	        return true;
		}
	 
	 private void updateViewPosition(){
	    //更新浮动窗口位置参数
	    wmParams.x=(int)( x-mTouchStartX);
	    wmParams.y=(int) (y-mTouchStartY);
	    wm.updateViewLayout(this, wmParams);  //刷新显示 
	 }

}

上面的wmParams变量(即WindowManager.LayoutParams)的存储采用了extends Application的方式来创建全局变量,示例代码如下:

public class MyApplication extends Application {
	
	/**
	 * 创建全局变量
	 * 全局变量一般都比较倾向于创建一个单独的数据类文件,并使用static静态变量
	 * 
	 * 这里使用了在Application中添加数据的方法实现全局变量
	 * 注意在AndroidManifest.xml中的Application节点添加android:name=".MyApplication"属性
	 * 
	 */
	private WindowManager.LayoutParams wmParams=new WindowManager.LayoutParams();

	public WindowManager.LayoutParams getMywmParams(){
		return wmParams;
	}
}

再来看一看Activity中的代码:

public class MyFloatViewActivity extends Activity {
    /** Called when the activity is first created. */
	
	private WindowManager wm=null;
	private WindowManager.LayoutParams wmParams=null;
	
	private MyFloatView myFV=null;

	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        //创建悬浮窗口
        createView();
    
    }
    
  
    
    private void createView(){
    	myFV=new MyFloatView(getApplicationContext());
    	myFV.setImageResource(R.drawable.icon);  //这里简单的用自带的Icom来做演示
    	//获取WindowManager
    	wm=(WindowManager)getApplicationContext().getSystemService("window");
        //设置LayoutParams(全局变量)相关参数
    	wmParams = ((MyApplication)getApplication()).getMywmParams();

         /**
         *以下都是WindowManager.LayoutParams的相关属性
         * 具体用途可参考SDK文档
         */
        wmParams.type=LayoutParams.TYPE_PHONE;   //设置window type
        wmParams.format=PixelFormat.RGBA_8888;   //设置图片格式,效果为背景透明

        //设置Window flag
        wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL
                              | LayoutParams.FLAG_NOT_FOCUSABLE;
        /*
         * 下面的flags属性的效果形同“锁定”。
         * 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。
         wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL 
                               | LayoutParams.FLAG_NOT_FOCUSABLE
                               | LayoutParams.FLAG_NOT_TOUCHABLE;
        */
        
        
        wmParams.gravity=Gravity.LEFT|Gravity.TOP;   //调整悬浮窗口至左上角,便于调整坐标
        //以屏幕左上角为原点,设置x、y初始值
        wmParams.x=0;
        wmParams.y=0;
        
        //设置悬浮窗口长宽数据
        wmParams.width=40;
        wmParams.height=40;
    
        //显示myFloatView图像
        wm.addView(myFV, wmParams);
 
    }
    
    @Override
    public void onDestroy(){
    	super.onDestroy();
    	//在程序退出(Activity销毁)时销毁悬浮窗口
    	wm.removeView(myFV);
    }    
}

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


这样一个可以置顶显示、悬浮、且可自由移动的窗口就完工了。运行一下,然后按Home键返回桌面试试看(不能点击返回键,演示程序这里设置了销毁窗体)

三、一些说明

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

而WindowManager.LayoutParams的属性就多了,非常丰富,这个也是关键所在。

这里例举两个window type:

        /**
         * 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;

可以看出TYPE_SYSTEM_ALERT的显示层次比TYPE_PHONE还要高,有兴趣的可以试一试显示效果哦!

另外关键的window flag:

        /** Window flag: this window won't ever get focus. */
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        
        /** Window flag: this window can never receive touch events. */
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        
        /** Window flag: Even when this window is focusable (its
         * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events
         * outside of the window to be sent to the windows behind it.  Otherwise
         * it will consume all pointer events itself, regardless of whether they
         * are inside of the window. */
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;

详细的可以看一下这里

最后,关于Android平台下的悬浮窗口,有人说很不友好,有人很困惑哪里会用到。事实上,在一些软件里面,悬浮窗口的设计给它们带来了很大的优势,比如流量监控,比如歌词显示。

给出源码包下载

首页      技术      Android      Android中可自由移动悬浮窗口的实现

发表回复

textsms
account_circle
email

  • gitar

    //获取相对View的坐标,即以此View左上角为原点
    25
    mTouchStartX = event.getX();
    26
    mTouchStartY = event.getY();
    你确定是这样的吗,你的程序运行没问题,但我觉得这两点说的不对,能程序运行没问题,太神奇了

    13 年前 回复
    • xsmile博主

      @gitar: 确实是这样的啊,依你的理解是什么意思呢?大家可以一起讨论嘛

      13 年前 回复
  • Qing

    很有帮助 想问一下在这个代码的基础上怎么实现onClick 试过好像是被拦截了 求解答 谢谢了

    13 年前 回复
    • xsmile博主

      @Qing: 可以通过识别用户操作来判断是否进行了单击,单击行为只会经历action down和action up这两个过程,所以代码判断一下用户操作是拖动还是单击。

      13 年前 回复
  • Qing

    额 能不能再问下在哪里加代码呢 刚学android。。。。

    13 年前 回复
  • lzan13

    能不能再这个悬浮框里加入动画,或者布局呢!楼主可否帮助一下!谢谢了!

    12 年前 回复
  • 新木

    我想在悬浮窗中增加输入功能 于是我添加了一个edittext 但是这个edittext无法调出输入法 我设置手动调出软键盘 但是也没有效果 不知道楼主是否有解决方法

    12 年前 回复
  • sh

    private WindowManager.LayoutParams wmParams = ((MyApplication)getContext().getApplicationContext()).getMywmParams();你确定你的程序能运行吗?我的不行,这句话有错,你的没吗?神奇

    12 年前 回复
    • xsmile博主

      @sh: 复制的代码?错误代码是什么?

      我的demo没问题。

      12 年前 回复
  • sh

    把父类转换成子类,再调用子类的方法,好像是这样的错误提醒,呵呵

    12 年前 回复
  • sh

    如果实现了悬浮,怎么回到原来的Activity呢,谢谢!

    12 年前 回复
  • ckzang

    寫的很好,對我幫助很大~謝謝您~~

    12 年前 回复
  • 我把继承的ImageView类 写成activity的一个内部类了 可是现在报错了 View has already been added to the window manager 我就不明白了

    12 年前 回复
    • xsmile博主

      @堂堂: 这个activity是不是在程序运行的时候已经创建了,这样的话再add是会报错的。window manager是总管,所有的view都由他创建。

      12 年前 回复
  • twuking

    这个悬浮框,能否做到能在状态栏上显示?

    12 年前 回复
  • 悬浮窗口的设计给它们带来了很大的优势,比如流量监控,比如歌词显示 这东西 用dialog popuwindow dialogActivity 不都可以替代吗

    11 年前 回复
  • zx

    悬浮窗可以实现TranslateAnimation动画效果吗

    11 年前 回复
  • cindy4611

    最后给出了”给出源码包下载”,下载不了demo.rar

    8 年前 回复
  • shijenwai

    private WindowManager wm=(WindowManager)getContext().getApplicationContext().getSystemService(“window”);
    請問這邊的”window”我demo時怎麼找不到

    8 年前 回复

xsmile's Blog

Android中可自由移动悬浮窗口的实现
第二届 Google 暑期大学生博客分享大赛 - 2011 Android 成长篇 本文为参加Google暑期大学生博客分享大赛特别撰写。 ---------------------------------------------------------------…
扫描二维码继续阅读
2011-07-27