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。就可以了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM