懸浮窗口的實現主要是用windowManager來實現的,為了簡單前面的基礎部分就copy過來:http://www.cnblogs.com/mythou/p/3244208.html
1、WindowManager介紹
全部Android的窗口機制是基於一個叫做WindowManager實現,這個接口可以添加view到屏幕,也可以從屏幕刪除view。它面向的對象一端是屏幕,另一端就是View,直接忽視我們以前的Activity或者Dialog之類的元素。其實我們的Activity或者Diolog底層的實現也是經過WindowManager,WindowManager是全局的,整個系統只有一個WindowManager。它是顯示View的最底層了。WindowManager主要用來管理窗口的一些狀態、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等。通過Context.getSystemService(Context.WINDOW_SERVICE)的方式可以獲得WindowManager的實例.WindowManager繼承自ViewManager,里面涉及到窗口管理的三個重要方法,分別是
- addView();
- updateViewLayout();
- removeView();
在WindowManager中還有一個重要的靜態類LayoutParams。通過它可以設置和獲得當前窗口的一些屬性。我們先來看看addView()方法,在addView中,會利用LayoutParams獲得window的View屬性,並為每個window創ViewRoot,ViewRoot是View和WindowManager之間的橋梁,真正把View傳遞給WindowManager的是通過ViewRoot的setView()方法,ViewRoot實現了View和WindowManager之間的消息傳遞。在將主窗口添加到WindowManger時,它首先會建立一個代理對象:
wm=(WindowManagerImpl)context.getSystemService(Context.WINDOW_SERVICE)
並且打開會話(IWindowSession),之后Window將通過該會話與WindowManager建立聯系。
2、關於android 6.0前后的授權問題。
可參考下:http://blog.csdn.net/self_study/article/details/50186435
本文主要是給一個服務添加懸浮窗口,首先判斷是否需要權限:
1、6.0前清單文件添加:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
2、6.0后除清單文件添加上面權限外還得手動為應用添加權限:
(1)在設置->應用->點開應用->配置應用->在其他應用的上層顯示 授予權限
(2)在代碼中判斷是否大於6.0版本,並判斷是否有權限,若沒有就跳到授權設置里。
(3)、將WindowManager 的類型設置為: params.type = WindowManager.LayoutParams.TYPE_TOAST;則不需要申請權限了
3、關於懸浮窗口的設置和實現以及一些屬性設置
(1)、懸浮窗口在某一個activity里顯示。
之前做過一個VR相冊的項目,要求點擊圖片時候顯示一張圖片,為了不創建新的activity和布局文件,只用一個自定義空間來顯示,采用了懸浮窗口,
relativeLayout =new RelativeLayout(this); WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.width = WindowManager.LayoutParams.MATCH_PARENT; params.height = WindowManager.LayoutParams.MATCH_PARENT; params.format = PixelFormat.RGBA_8888;//背景透明 params.gravity = Gravity.LEFT | Gravity.TOP; relativeLayout.addView(myBallPictureView); wm.addView(relativeLayout, params);
然而發現了懸浮窗口的2個坑:
<1> 、在addview()的時候報錯:android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application,根據報錯的原因,是窗口的令牌錯誤,然后查看我的windowManager的初始化為:
wm = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
既然是令牌錯誤,就發現可能是初始化wm時候發生了錯誤,后來通過activity和getApplicationContext()的contex是不一樣的,一個是整個應用的,一個是此activity的,后來換作activity來獲取WM,發現不再報錯,且顯示很流暢。getApplicationContext()除非添加了
params.type = WindowManager.LayoutParams.TYPE_TOAST;后才不報錯,但會一直顯示在屏幕上。。其實這里提醒我們能使用activity的context盡量別使用getApplicationContext(),還是有區別的(雖然大部分都時候獲取的token其實是一樣的)。
<2>、發現彈出了圖像后按back鍵圖像消失不了,為什么消失不了?通過log發現,當我按onBackPress()的時候發現,我覆寫的onBackPress()方法更本沒有執行,就聯想到可能是WindowManager.LayoutParams 的Type屬性問題了。后來將Type修改為:
params.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
發現可以了。
這里也使得我們在使用懸浮窗口的時候主要的事項:WindowManager.LayoutParams的屬性,和context的獲取。下面介紹一下WindowManager.LayoutParams的Type和flag2個重要的屬性:
Type是添加窗口的類型:窗口以系統的什么類型的窗口顯示:可參考:http://realgodo.iteye.com/blog/1780176。用的較多的就是第二部分提到的TYPE_TOAST。此時會顯示在屏幕上,
flag窗口的焦點屬性:
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;此時不會固定在屏幕上,按返回鍵是有效的。
WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 此時窗口固定在屏幕上,返回鍵是無效的。
(2) 懸浮窗口在所有屏幕上顯示
這其實上面也提到了,需要設置TYPE_TOAST,或者TYPE_PRIORITY_PHONE 具體參看上面的關於Type的博客。
(3),應用內顯示懸浮窗口
這里為了方便管理,通常是設置一個service來實現,當應用前台時候調用服務顯示窗口,當應用后台時候調用服務顯示窗口。
(4)、桌面顯示,應用內不現實:這個和上面相反,也可以用服務來實現。
由於時間緊,直接給出(3)代碼,其中有注解:
Activity:
package com.example.user.floatingview; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button btn_show; private Button btn_hide; private int floatShow = 0; private boolean hasGetPermission = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn_show = (Button) findViewById(R.id.btn_show); btn_hide = (Button) findViewById(R.id.btn_hide); btn_show.setOnClickListener(this); btn_hide.setOnClickListener(this); } public void onClick(View v) { switch (v.getId()) { case R.id.btn_show: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){//判斷api版本,若低於6.0就不需要動態授權檢測權限,將canDrawOverlays版本disable掉 hasGetPermission =Settings.canDrawOverlays(this); if (!hasGetPermission) { Toast.makeText(this, "當前無權限,請授權!", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, 1024); } else { floatShow = 1; Toast.makeText(this, "后台顯示浮動窗口", Toast.LENGTH_SHORT).show(); } } break; case R.id.btn_hide: floatShow = -1; Toast.makeText(this,"已取消后台顯示浮動窗口",Toast.LENGTH_SHORT).show(); break; } } @Override protected void onPause() { super.onPause(); if(hasGetPermission) { if (floatShow == 1) { Intent show = new Intent(this, TopWindowService.class); show.putExtra(TopWindowService.OPERATION, TopWindowService.OPERATION_SHOW); startService(show); } else if (floatShow == -1) { Intent hide = new Intent(this, TopWindowService.class); hide.putExtra(TopWindowService.OPERATION, TopWindowService.OPERATION_HIDE); startService(hide); } } } @Override protected void onResume() { super.onResume(); if(hasGetPermission&&floatShow == 1) { Intent hide = new Intent(this, TopWindowService.class); hide.putExtra(TopWindowService.OPERATION, TopWindowService.OPERATION_HIDE); startService(hide); } } protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 1024&&(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)){ if(!Settings.canDrawOverlays(this)) { Toast.makeText(this, "權限授予失敗,無法開啟懸浮窗", Toast.LENGTH_SHORT).show(); hasGetPermission = false; } else { Toast.makeText(this, "權限授予成功!", Toast.LENGTH_SHORT).show(); floatShow = 1; } } } }
service:
package com.example.user.floatingview;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.Button;
public class TopWindowService extends Service
{
public static final String OPERATION = "operation";
public static final int OPERATION_SHOW = 100;
public static final int OPERATION_HIDE = 101;
private static final int HANDLE_CHECK_ACTIVITY = 200;
private boolean isAdded = false; // 是否已增加懸浮窗
private static WindowManager wm;
private static WindowManager.LayoutParams params;
private Button btn_floatView;
@Override
public IBinder onBind(Intent intent)
{
return null;
}
@Override
public void onCreate()
{
super.onCreate();
createFloatView();
}
@Override
public void onDestroy()
{
super.onDestroy();
}
@Override
public void onStart(Intent intent, int startId)
{
super.onStart(intent, startId);
int operation = intent.getIntExtra(OPERATION, OPERATION_SHOW);
switch (operation)
{
case OPERATION_SHOW:
mHandler.sendEmptyMessage(OPERATION_SHOW);
break;
case OPERATION_HIDE:
mHandler.sendEmptyMessage(OPERATION_HIDE);
break;
}
}
private Handler mHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case OPERATION_SHOW:
Log.v("zp","receive show message ");
if (!isAdded)
{
wm.addView(btn_floatView, params);
isAdded = true;
}
break;
case OPERATION_HIDE:
Log.v("zp","receive hide message ");
if (isAdded)
{
wm.removeView(btn_floatView);
isAdded = false;
}
break;
}
}
};
/**
* 創建懸浮窗
*/
private void createFloatView()
{
btn_floatView = new Button(getApplicationContext());
btn_floatView.setText("hello");
btn_floatView.setBackgroundResource(R.drawable.earth);
btn_floatView.setAlpha(0.5f);//設置透明
wm = (WindowManager) getApplicationContext().getSystemService(
Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams();
// 設置window type
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
// 若設置為TYPE_TOAST 則不需要申請權限
//params.type = WindowManager.LayoutParams.TYPE_TOAST;
/*
* 如果設置為params.type = WindowManager.LayoutParams.TYPE_PHONE; 那么優先級會降低一些,
* 即拉下通知欄不可見
*/
params.format = PixelFormat.RGBA_8888; // 設置圖片格式
// 設置Window flag
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
/*
* 下面的flags屬性的效果形同“鎖定”。 懸浮窗不可觸摸,不接受任何事件,同時不影響后面的事件響應。
* wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL |
* LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE;
*/
// 設置懸浮窗的長得寬
params.width = 150;
params.height = 120;
// 設置懸浮窗的Touch監聽
btn_floatView.setOnTouchListener(new OnTouchListener()
{
int lastX, lastY;
int paramX, paramY;
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
lastX = (int) event.getRawX();
lastY = (int) event.getRawY();
paramX = params.x;
paramY = params.y;
break;
case MotionEvent.ACTION_MOVE:
int dx = (int) event.getRawX() - lastX;
int dy = (int) event.getRawY() - lastY;
params.x = paramX + dx;
params.y = paramY + dy;
// 更新懸浮窗位置
wm.updateViewLayout(btn_floatView, params);
break;
}
return true;
}
});
wm.addView(btn_floatView, params);
isAdded = true;
}
}
main_activity.xml
<?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/btn_show" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="顯示桌面懸浮窗" /> <Button android:id="@+id/btn_hide" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="隱藏桌面懸浮窗" /> </LinearLayout>
