Android 懸浮視窗的實現

小二陽發表於2017-12-14

CompatModeWrapper:該類就是實現懸浮視窗的重要類

1.CompatModeWrapper相當於是一個殼,而真正實現大部分功能的是它裡面的成員變數mWindowManager(WindowManagerImpl類)。

2.該物件可以通過  getApplication().getSystemService(Context.WINDOW_SERVICE)  得到。(注:如果是通過       activity.getSystemService(Context.WINDOW_SERVICE)得到的只是屬於Activity的LocalWindowManager)。

3.這個物件的建立是在每個程式開始的時候, 通過ContextImpl中的靜態程式碼塊建立的, 它使用了單例模式, 保證每個application只有一個。

4.通過該類可以實現建立新增懸浮視窗,也就是說,在退出當前Activity時,通過該類建立的檢視還是可見的,它是屬於整個應用程式的檢視,存活在程式中,不受Activity的生命週期影響。 具體的實現操作可以在Activity或者Service中(這兩者都是可以建立存活在應用程式中的Android重要元件)實現。 通過主Activity的啟動按鈕,啟動一個Service,然後在Service中建立新增懸浮視窗:要獲取CompatModeWrapper,首先得在應用程式的AndroidManifest.xml檔案中新增許可權

uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW"/ MainActivity的程式碼如下:

publicclassMainActivityextendsActivity {
      @Override
      publicvoidonCreate(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(newOnClickListener() {
           @Override
           publicvoidonClick(View v) {
                            Intent intent =newIntent(MainActivity.this, FxService.class);
                            //啟動FxService
                            startService(intent);
                            finish();
}
});
       remove.setOnClickListener(newOnClickListener()  {
                 @Override
                publicvoidonClick(View v){
                                  Intent intent =newIntent(MainActivity.this, FxService.class);
                                   //終止FxService
                                 stopService(intent);
}
});
}
}
複製程式碼

FloatService的程式碼如下:

importandroid.app.Service;
importandroid.content.Intent;
importandroid.graphics.PixelFormat;
importandroid.os.Handler;
importandroid.os.IBinder;
importandroid.util.Log;
importandroid.view.Gravity;
importandroid.view.LayoutInflater;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.WindowManager;
importandroid.view.View.OnClickListener;
importandroid.view.View.OnTouchListener;
importandroid.view.WindowManager.LayoutParams;
importandroid.widget.Button;
importandroid.widget.LinearLayout;
importandroid.widget.Toast;
publicclassFxServiceextendsService  {
//定義浮動視窗布局
LinearLayout mFloatLayout;
WindowManager.LayoutParams wmParams;
//建立浮動視窗設定佈局引數的物件
WindowManager mWindowManager;
Button mFloatView;
privatestaticfinalString TAG ="FxService";
@Override
publicvoidonCreate()
{
// TODO Auto-generated method stub
super.onCreate();
Log.i(TAG,"oncreat");
createFloatView();
}
@Override
publicIBinder onBind(Intent intent)
{
// TODO Auto-generated method stub
returnnull;
}
privatevoidcreateFloatView()
{
wmParams =newWindowManager.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(newOnTouchListener()
{
@Override
publicbooleanonTouch(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);
returnfalse;//此處必須返回false,否則OnClickListener獲取不到監聽
}
});
mFloatView.setOnClickListener(newOnClickListener()
{
@Override
publicvoidonClick(View v)
{
// TODO Auto-generated method stub
Toast.makeText(FxService.this,"onClick", Toast.LENGTH_SHORT).show();
}
});
}
@Override
publicvoidonDestroy()
{
// TODO Auto-generated method stub
super.onDestroy();
if(mFloatLayout !=null)
{
//移除懸浮視窗
mWindowManager.removeView(mFloatLayout);
}
}
}
複製程式碼

懸浮視窗的佈局檔案為R.layout.float_layout,所以,如果我們想設計一個非常美觀的懸浮視窗,可以在該佈局檔案裡編寫。當然,也可以使用自定義View來設計(哈哈,少年們,在此基礎上發揮想象吧)。 上面程式碼的效果圖如下:左邊為啟動介面。點選“啟動懸浮視窗”按鈕,會啟動後臺service建立懸浮視窗,同時finish當前Activity,這樣一個懸浮視窗就建立出來了,該視窗可實現任意位置移動,且可點選監聽建立Toast提示(當然,也可以啟動一個Activity)。若要移除已建立的視窗,可點選“移除懸浮視窗按鈕”,或者強制禁止該應用程式。 懸浮視窗的佈局檔案為R.layout.float_layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    
                  android:layout_width="fill_parent"    
                  android:layout_height="fill_parent"    
                  android:orientation="vertical" >
           <Button
                        android:id="@+id/start_id"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="@string/btn"/>
              <Button
                        android:id="@+id/remove_id"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="懸浮視窗"/>
</LinearLayout>
           
           MainActivity 的佈局檔案
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="fill_parent"
                  android:layout_height="fill_parent"
                  android:orientation="vertical" >
                  <Button
                       android:id="@+id/start_id"
                       android:layout_width="wrap_content"
                       android:layout_height="wrap_content"
                       android:text="啟動懸浮視窗"/>
                  <Button
                       android:id="@+id/remove_id"
                       android:layout_width="wrap_content"
                       android:layout_height="wrap_content"
                       android:text="移除懸浮視窗"/>
</LinearLayout>
複製程式碼

特別注意:有的6.0以上(包括6.0)的手機需要手動開啟懸浮窗許可權 ,這時候如果 我們不想使用者去手動開啟,也就是強制開啟懸浮窗,我們將 FloatService 裡的這行程式碼:

//設定window type
wmParams.type = LayoutParams.TYPE_PHONE;
複製程式碼

換成

//設定window type
wmParams.type =  LayoutParams.TYPE_TOAST;//無需許可權
複製程式碼

這樣就不會跳到使用者需要手動開啟懸浮視窗的頁面了,是不是很簡單0.0


Android 懸浮視窗的實現

Android 懸浮視窗的實現

//需要許可權,且在某些系統中還需要手動開啟設定,比如miui

wmParams.type = LayoutParams.TYPE_PHONE;

//無需許可權

wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;

如果還有疑問歡迎探討0.0

相關文章