大家对悬浮窗概念不会陌生,相信每台电脑桌面的右上角都会有这么一个东西,它总是出现在所有页面的顶端(Top Show)。但在Android平台中如何实现这样的效果呢?先来看一看效果图。
看见在Google搜索框上面的那个Icon图片了嘛。下面我就来详细介绍一下在Android平台下悬浮窗口的实现,并让它能够随手指的触摸而移动。
一、实现原理及移动思路
调用WindowManager,并设置WindowManager.LayoutParams的相关属性,通过WindowManager的addView方法创建View,这样产生出来的View根据WindowManager.LayoutParams属性不同,效果也就不同了。比如创建系统顶级窗口,实现悬浮窗口效果!然后通过覆写悬浮View中onTouchEvent方法来改变windowMananager.LayoutParams中x和y的值来实现自由移动悬浮窗口。
二、示例代码
先来看一看悬浮View的代码,这里用一个ImageView作为演示
1 public class MyFloatView extends ImageView { 2 3 private float mTouchStartX; 4 5 private float mTouchStartY; 6 7 private float x; 8 9 private float y; 10 11 12 13 private WindowManager wm=(WindowManager)getContext().getApplicationContext().getSystemService("window"); 14 15 //此wmParams变量为获取的全局变量,用以保存悬浮窗口的属性 16 09 17 private WindowManager.LayoutParams wmParams = ((MyApplication)getContext().getApplicationContext()).getMywmParams(); 18 10 19 20 11 21 public MyFloatView(Context context) { 22 12 23 super(context); 24 13 25 // TODO Auto-generated constructor stub 26 14 27 } 28 15 29 30 16 31 @Override 32 17 33 public boolean onTouchEvent(MotionEvent event) { 34 18 35 //获取相对屏幕的坐标,即以屏幕左上角为原点 36 19 37 x = event.getRawX(); 38 20 39 y = event.getRawY()-25; //25是系统状态栏的高度 40 21 41 Log.i("currP", "currX"+x+"====currY"+y); 42 22 43 switch (event.getAction()) { 44 23 45 case MotionEvent.ACTION_DOWN: //捕获手指触摸按下动作 46 24 47 //获取相对View的坐标,即以此View左上角为原点 48 25 49 mTouchStartX = event.getX(); 50 26 51 mTouchStartY = event.getY(); 52 27 53 Log.i("startP","startX"+mTouchStartX+"====startY"+mTouchStartY); 54 28 55 break; 56 29 57 58 30 59 case MotionEvent.ACTION_MOVE: //捕获手指触摸移动动作 60 31 61 updateViewPosition(); 62 32 63 break; 64 33 65 66 34 67 case MotionEvent.ACTION_UP: //捕获手指触摸离开动作 68 35 69 updateViewPosition(); 70 36 71 mTouchStartX=mTouchStartY=0; 72 37 73 break; 74 38 75 } 76 39 77 return true; 78 40 79 } 80 41 81 82 42 83 private void updateViewPosition(){ 84 43 85 //更新浮动窗口位置参数 86 44 87 wmParams.x=(int)( x-mTouchStartX); 88 45 89 wmParams.y=(int) (y-mTouchStartY); 90 46 91 wm.updateViewLayout(this, wmParams); //刷新显示 92 47 93 } 94 48 95 96 49 97 }
上面的wmParams变量(即WindowManager.LayoutParams)的存储采用了extends Application的方式来创建全局变量,示例代码如下:
1 public class MyApplication extends Application { 2 02 3 4 03 5 /** 6 04 7 * 创建全局变量 8 05 9 * 全局变量一般都比较倾向于创建一个单独的数据类文件,并使用static静态变量 10 06 11 * 12 07 13 * 这里使用了在Application中添加数据的方法实现全局变量 14 08 15 * 注意在AndroidManifest.xml中的Application节点添加android:name=".MyApplication"属性 16 09 17 * 18 10 19 */ 20 11 21 private WindowManager.LayoutParams wmParams=newWindowManager.LayoutParams(); 22 12 23 24 13 25 public WindowManager.LayoutParams getMywmParams(){ 26 14 27 return wmParams; 28 15 29 } 30 16 31 }
再来看一看Activity中的代码:
1 public class MyFloatViewActivity extends Activity { 2 02 3 /** Called when the activity is first created. */ 4 03 5 6 04 7 private WindowManager wm=null; 8 05 9 private WindowManager.LayoutParams wmParams=null; 10 06 11 12 07 13 private MyFloatView myFV=null; 14 08 15 16 09 17 18 10 19 @Override 20 11 21 public void onCreate(Bundle savedInstanceState) { 22 12 23 super.onCreate(savedInstanceState); 24 13 25 setContentView(R.layout.main); 26 14 27 //创建悬浮窗口 28 15 29 createView(); 30 16 31 32 17 33 } 34 18 35 36 19 37 38 20 39 40 21 41 private void createView(){ 42 22 43 myFV=new MyFloatView(getApplicationContext()); 44 23 45 myFV.setImageResource(R.drawable.icon); //这里简单的用自带的Icom来做演示 46 24 47 //获取WindowManager 48 25 49 wm=(WindowManager)getApplicationContext().getSystemService("window"); 50 26 51 //设置LayoutParams(全局变量)相关参数 52 27 53 wmParams = ((MyApplication)getApplication()).getMywmParams(); 54 28 55 56 29 57 /** 58 30 59 *以下都是WindowManager.LayoutParams的相关属性 60 31 61 * 具体用途可参考SDK文档 62 32 63 */ 64 33 65 wmParams.type=LayoutParams.TYPE_PHONE; //设置window type 66 34 67 wmParams.format=PixelFormat.RGBA_8888; //设置图片格式,效果为背景透明 68 35 69 70 36 71 //设置Window flag 72 37 73 wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL 74 38 75 | LayoutParams.FLAG_NOT_FOCUSABLE; 76 39 77 /* 78 40 79 * 下面的flags属性的效果形同“锁定”。 80 41 81 * 悬浮窗不可触摸,不接受任何事件,同时不影响后面的事件响应。 82 42 83 wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL 84 43 85 | LayoutParams.FLAG_NOT_FOCUSABLE 86 44 87 | LayoutParams.FLAG_NOT_TOUCHABLE; 88 45 89 */ 90 46 91 92 47 93 94 48 95 wmParams.gravity=Gravity.LEFT|Gravity.TOP; //调整悬浮窗口至左上角,便于调整坐标 96 49 97 //以屏幕左上角为原点,设置x、y初始值 98 50 99 wmParams.x=0; 100 51 101 wmParams.y=0; 102 52 103 104 53 105 //设置悬浮窗口长宽数据 106 54 107 wmParams.width=40; 108 55 109 wmParams.height=40; 110 56 111 112 57 113 //显示myFloatView图像 114 58 115 wm.addView(myFV, wmParams); 116 59 117 118 60 119 } 120 61 121 122 62 123 @Override 124 63 125 public void onDestroy(){ 126 64 127 super.onDestroy(); 128 65 129 //在程序退出(Activity销毁)时销毁悬浮窗口 130 66 131 wm.removeView(myFV); 132 67 133 } 134 68 135 }
最后,别忘了在AndroidManifest.xml中添加权限:
1 <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"> 2 2 3 </uses-permission>
这样一个可以置顶显示、悬浮、且可自由移动的窗口就完工了。运行一下,然后按Home键返回桌面试试看(不能点击返回键,演示程序这里设置了销毁窗体)
三、一些说明
WindowManager的方法很简单,基本用到的就三个addView,removeView,updateViewLayout。
而WindowManager.LayoutParams的属性就多了,非常丰富,这个也是关键所在。
这里例举两个window type:
1 /** 2 02 3 * Window type: phone. These are non-application windows providing 4 03 5 * user interaction with the phone (in particular incoming calls). 6 04 7 * These windows are normally placed above all applications, but behind 8 05 9 * the status bar. 10 06 11 */ 12 07 13 public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2; 14 08 15 16 09 17 /** 18 10 19 * Window type: system window, such as low power alert. These windows 20 11 21 * are always on top of application windows. 22 12 23 */ 24 13 25 public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
可以看出TYPE_SYSTEM_ALERT的显示层次比TYPE_PHONE还要高,有兴趣的可以试一试显示效果哦!
另外关键的window flag:
1 /** Window flag: this window won‘t ever get focus. */ 2 02 3 public static final int FLAG_NOT_FOCUSABLE = 0x00000008; 4 03 5 6 04 7 /** Window flag: this window can never receive touch events. */ 8 05 9 public static final int FLAG_NOT_TOUCHABLE = 0x00000010; 10 06 11 12 07 13 /** Window flag: Even when this window is focusable (its 14 08 15 * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events 16 09 17 * outside of the window to be sent to the windows behind it. Otherwise 18 10 19 * it will consume all pointer events itself, regardless of whether they 20 11 21 * are inside of the window. */ 22 12 23 public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
详细的可以看一下这里。
最后,关于Android平台下的悬浮窗口,有人说很不友好,有人很困惑哪里会用到。事实上,在一些软件里面,悬浮窗口的设计给它们带来了很大的优势,比如流量监控,比如歌词显示。
给出源码包下载
转自:http://blog.csdn.net/fancylovejava/article/details/17891977