第二屆 Google 暑期大學生博客分享大賽 – 2011 Android 成長篇

本文為參加Google暑期大學生博客分享大賽特別撰寫,。


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

大家對懸浮窗概念不會陌生,,相信每臺電腦桌面的右上角都會有這么一個東西,,它總是出現(xiàn)在所有頁面的頂端(Top Show),。但在Android平臺中如何實現(xiàn)這樣的效果呢,?先來看一看效果圖。

FloatView

看見在Google搜索框上面的那個Icon圖片了嘛,。下面我就來詳細介紹一下在Android平臺下懸浮窗口的實現(xiàn),,并讓它能夠隨手指的觸摸而移動。

一,、實現(xiàn)原理及移動思路

調(diào)用WindowManager,,并設置WindowManager.LayoutParams的相關屬性,通過WindowManager的addView方法創(chuàng)建View,,這樣產(chǎn)生出來的View根據(jù)WindowManager.LayoutParams屬性不同,,效果也就不同了。比如創(chuàng)建系統(tǒng)頂級窗口,,實現(xiàn)懸浮窗口效果,!然后通過覆寫懸浮View中onTouchEvent方法來改變windowMananager.LayoutParams中x和y的值來實現(xiàn)自由移動懸浮窗口。

二,、示例代碼

先來看一看懸浮View的代碼,,這里用一個ImageView作為演示

01 public class MyFloatView extends ImageView {
02     private float mTouchStartX;
03     private float mTouchStartY;
04     private float x;
05     private float y;
06       
07     private WindowManager wm=(WindowManager)getContext().getApplicationContext().getSystemService("window");
08     //此wmParams變量為獲取的全局變量,用以保存懸浮窗口的屬性
09     private WindowManager.LayoutParams wmParams = ((MyApplication)getContext().getApplicationContext()).getMywmParams();
10   
11     public MyFloatView(Context context) {
12         super(context);     
13         // TODO Auto-generated constructor stub
14     }
15       
16      @Override
17      public boolean onTouchEvent(MotionEvent event) {
18          //獲取相對屏幕的坐標,,即以屏幕左上角為原點      
19          x = event.getRawX();   
20          y = event.getRawY()-25;   //25是系統(tǒng)狀態(tài)欄的高度
21          Log.i("currP", "currX"+x+"====currY"+y);
22          switch (event.getAction()) {
23             case MotionEvent.ACTION_DOWN:    //捕獲手指觸摸按下動作
24                 //獲取相對View的坐標,,即以此View左上角為原點
25                 mTouchStartX =  event.getX();  
26                     mTouchStartY =  event.getY();                
27                 Log.i("startP", "startX"+mTouchStartX+"====startY"+mTouchStartY);
28                 break;
29   
30             case MotionEvent.ACTION_MOVE:   //捕獲手指觸摸移動動作            
31                 updateViewPosition();
32                 break;
33   
34             case MotionEvent.ACTION_UP:    //捕獲手指觸摸離開動作
35                 updateViewPosition();
36                 mTouchStartX=mTouchStartY=0;
37                 break;
38             }
39             return true;
40         }
41        
42      private void updateViewPosition(){
43         //更新浮動窗口位置參數(shù)
44         wmParams.x=(int)( x-mTouchStartX);
45         wmParams.y=(int) (y-mTouchStartY);
46         wm.updateViewLayout(this, wmParams);  //刷新顯示 
47      }
48   
49 }

上面的wmParams變量(即WindowManager.LayoutParams)的存儲采用了extends Application的方式來創(chuàng)建全局變量,示例代碼如下:

01 public class MyApplication extends Application {
02       
03     /**
04      * 創(chuàng)建全局變量
05      * 全局變量一般都比較傾向于創(chuàng)建一個單獨的數(shù)據(jù)類文件,并使用static靜態(tài)變量
06      
07      * 這里使用了在Application中添加數(shù)據(jù)的方法實現(xiàn)全局變量
08      * 注意在AndroidManifest.xml中的Application節(jié)點添加android:name=".MyApplication"屬性
09      
10      */
11     private WindowManager.LayoutParams wmParams=new WindowManager.LayoutParams();
12   
13     public WindowManager.LayoutParams getMywmParams(){
14         return wmParams;
15     }
16 }

再來看一看Activity中的代碼:

01 public class MyFloatViewActivity extends Activity {
02     /** Called when the activity is first created. */
03       
04     private WindowManager wm=null;
05     private WindowManager.LayoutParams wmParams=null;
06       
07     private MyFloatView myFV=null;
08   
09       
10     @Override
11     public void onCreate(Bundle savedInstanceState) {
12         super.onCreate(savedInstanceState);
13         setContentView(R.layout.main);
14         //創(chuàng)建懸浮窗口
15         createView();
16       
17     }
18       
19     
20       
21     private void createView(){
22         myFV=new MyFloatView(getApplicationContext());
23         myFV.setImageResource(R.drawable.icon);  //這里簡單的用自帶的Icom來做演示
24         //獲取WindowManager
25         wm=(WindowManager)getApplicationContext().getSystemService("window");
26         //設置LayoutParams(全局變量)相關參數(shù)
27         wmParams = ((MyApplication)getApplication()).getMywmParams();
28   
29          /**
30          *以下都是WindowManager.LayoutParams的相關屬性
31          * 具體用途可參考SDK文檔
32          */
33         wmParams.type=LayoutParams.TYPE_PHONE;   //設置window type
34         wmParams.format=PixelFormat.RGBA_8888;   //設置圖片格式,,效果為背景透明
35   
36         //設置Window flag
37         wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL
38                               | LayoutParams.FLAG_NOT_FOCUSABLE;
39         /*
40          * 下面的flags屬性的效果形同“鎖定”,。
41          * 懸浮窗不可觸摸,不接受任何事件,同時不影響后面的事件響應,。
42          wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL 
43                                | LayoutParams.FLAG_NOT_FOCUSABLE
44                                | LayoutParams.FLAG_NOT_TOUCHABLE;
45         */
46           
47           
48         wmParams.gravity=Gravity.LEFT|Gravity.TOP;   //調(diào)整懸浮窗口至左上角,,便于調(diào)整坐標
49         //以屏幕左上角為原點,設置x,、y初始值
50         wmParams.x=0;
51         wmParams.y=0;
52           
53         //設置懸浮窗口長寬數(shù)據(jù)
54         wmParams.width=40;
55         wmParams.height=40;
56       
57         //顯示myFloatView圖像
58         wm.addView(myFV, wmParams);
59    
60     }
61       
62     @Override
63     public void onDestroy(){
64         super.onDestroy();
65         //在程序退出(Activity銷毀)時銷毀懸浮窗口
66         wm.removeView(myFV);
67     }    
68 }

最后,,別忘了在AndroidManifest.xml中添加權限:

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

這樣一個可以置頂顯示、懸浮,、且可自由移動的窗口就完工了,。運行一下,然后按Home鍵返回桌面試試看(不能點擊返回鍵,,演示程序這里設置了銷毀窗體)

三,、一些說明

WindowManager的方法很簡單,基本用到的就三個addView,,removeView,,updateViewLayout。

而WindowManager.LayoutParams的屬性就多了,,非常豐富,,這個也是關鍵所在。

這里例舉兩個window type:

01 /**
02  * Window type: phone.  These are non-application windows providing
03  * user interaction with the phone (in particular incoming calls).
04  * These windows are normally placed above all applications, but behind
05  * the status bar.
06  */
07 public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
08 /**
09  * Window type: system window, such as low power alert. These windows
10  * are always on top of application windows.
11  */
12 public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;

可以看出TYPE_SYSTEM_ALERT的顯示層次比TYPE_PHONE還要高,,有興趣的可以試一試顯示效果哦,!

另外關鍵的window flag:

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

詳細的可以看一下這里

最后,,關于Android平臺下的懸浮窗口,,有人說很不友好,有人很困惑哪里會用到,。事實上,,在一些軟件里面,懸浮窗口的設計給它們帶來了很大的優(yōu)勢,,比如流量監(jiān)控,,比如歌詞顯示。

給出源碼包下載