Android懸浮窗實現 使用WindowManager

來源:互聯網
上載者:User

標籤:

本文轉載自: 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()         {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubIntent intent = new Intent(MainActivity.this, FxService.class);//啟動FxServicestartService(intent);finish();}});                remove.setOnClickListener(new OnClickListener()         {@Overridepublic void onClick(View v) {//uninstallApp("com.phicomm.hu");Intent intent = new Intent(MainActivity.this, FxService.class);//終止FxServicestopService(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";@Overridepublic void onCreate() {// TODO Auto-generated method stubsuper.onCreate();Log.i(TAG, "oncreat");createFloatView();}@Overridepublic IBinder onBind(Intent intent){// TODO Auto-generated method stubreturn null;}private void createFloatView(){wmParams = new WindowManager.LayoutParams();//擷取的是WindowManagerImpl.CompatModeWrappermWindowManager = (WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);Log.i(TAG, "mWindowManager--->" + mWindowManager);//設定window typewmParams.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()         {@Overridepublic 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()         {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubToast.makeText(FxService.this, "onClick", Toast.LENGTH_SHORT).show();}});}@Overridepublic void onDestroy() {// TODO Auto-generated method stubsuper.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()         {@Overridepublic void onClick(View v){// TODO Auto-generated method stubcreateFloatView();//finish();//handle.post(r);}});                stop.setOnClickListener(new OnClickListener()        {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubif(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()         {@Overridepublic boolean onTouch(View v, MotionEvent event) {// TODO Auto-generated method stubwmParams.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()        {@Overridepublic void onClick(View v) {// TODO Auto-generated method stubIntent 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。就可以了

Android懸浮窗實現 使用WindowManager

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.