Android桌面懸浮視窗舉例
概述
Android專案開發時,有時候需要開發一些懸浮在桌面上的檢視。比如桌面小精靈,各種音樂播放器的懸浮播放控制欄等等。本文就藉助一個小的demo,用程式碼的方式大概進行介紹。
原理
開發桌面懸浮視窗一般遵循兩個大的原則。
1.最根本的原則是採用WindowManager類,WindowManager有addView(View view, ViewGroup.LayoutParams params)方法,改方法可以直接在Android根窗口布局中新增一個View檢視。這裡的view其實就是我們要在桌面上顯示的檢視。
同理,如果我們要讓這個檢視消失掉,就可以呼叫WindowManager的removeView(View view)方法。
2.另外一個原則是,需要藉助service。因為service是沒有介面的,如果在activity中進行的話,當前的介面會擋住這個懸浮框。
好了,話不多說直接上程式碼。
關鍵程式碼展示
需要展示的View檢視的佈局layout_float_window.xml
<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/small_window_layout"
android:layout_width="match_parent"
android:layout_height="80dp"
android:background ="#f4f2f3"
android:paddingLeft="@dimen/margin_20"
android:paddingRight="@dimen/margin_20">
<ImageView
android:layout_width="@dimen/margin_60"
android:layout_height="@dimen/margin_60"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_microphone_white" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="70dp"
android:layout_marginRight="50dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingExtra="0sp"
android:text="咪咕家語音播報"
android:textColor="#282e33"
android:textSize="17sp" />
<TextView
android:id="@+id/percent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:lineSpacingExtra="0sp"
android:text="IM訊息"
android:textColor="#66737f"
android:textSize="14sp" />
</LinearLayout>
<ImageView
android:id="@+id/iv_close_notify"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical|right"
android:scaleType="center"
android:src="@drawable/ic_close_grey1" />
</FrameLayout>
需要展示的View檢視類FloatWindow.java
public class FloatWindow extends LinearLayout {
public static int viewWidth; //懸浮窗的寬度
public static int viewHeight; //懸浮窗的高度
private static int statusBarHeight; //系統狀態列的高度
private WindowManager windowManager; //用於更新懸浮窗的位置
private WindowManager.LayoutParams mParams; //懸浮窗的引數
private float xInScreen; //當前手指位置在螢幕上的橫座標值
private float yInScreen; //當前手指位置在螢幕上的縱座標值
private float xDownInScreen; //手指按下時在螢幕上的橫座標的值
private float yDownInScreen; //手指按下時在螢幕上的縱座標的值
private float xInView; //手指按下時在懸浮窗的View上的橫座標的值
private float yInView;//手指按下時在懸浮窗的View上的縱座標的值
private TextView tittleView; //標題
private TextView contentView; //內容
private ImageView closeView; //關閉按鈕
public FloatWindow(final Context context) {
super(context);
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
LayoutInflater.from(context).inflate(R.layout.layout_float_window, this);
View view = findViewById(R.id.small_window_layout);
viewWidth = view.getLayoutParams().width;
viewHeight = view.getLayoutParams().height;
contentView = (TextView) findViewById(R.id.percent);
contentView.setText("IM訊息");
closeView = (ImageView) findViewById(R.id.iv_close_notify);
closeView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
FloatWindowManager.removeFloatWindow(context);
Intent intent = new Intent(getContext(), VoiceBroadcastService.class);
context.stopService(intent);
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 手指按下時記錄必要資料,縱座標的值都需要減去狀態列高度
xInView = event.getX();
yInView = event.getY();
xDownInScreen = event.getRawX();
yDownInScreen = event.getRawY() - getStatusBarHeight();
xInScreen = event.getRawX();
yInScreen = event.getRawY() - getStatusBarHeight();
break;
case MotionEvent.ACTION_MOVE:
xInScreen = event.getRawX();
yInScreen = event.getRawY() - getStatusBarHeight();
updateViewPosition();
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return true;
}
/**
* 將懸浮窗的引數傳入,用於更新懸浮窗的位置。
*
* @param params 懸浮窗的引數
*/
public void setParams(WindowManager.LayoutParams params) {
mParams = params;
}
/**
* 更新懸浮窗在螢幕中的位置(手指拖動)
*/
private void updateViewPosition() {
mParams.x = (int) (xInScreen - xInView);
mParams.y = (int) (yInScreen - yInView);
windowManager.updateViewLayout(this, mParams);
}
/**
* 用於獲取狀態列的高度。
*
* @return 返回狀態列高度的畫素值。
*/
private int getStatusBarHeight() {
if (statusBarHeight == 0) {
try {
Class<?> c = Class.forName("com.android.internal.R$dimen");
Object o = c.newInstance();
Field field = c.getField("status_bar_height");
int x = (Integer) field.get(o);
statusBarHeight = getResources().getDimensionPixelSize(x);
} catch (Exception e) {
e.printStackTrace();
}
}
return statusBarHeight;
}
}
Windowmanager管理類,該類是懸浮視窗的主要操作類。可以在桌面新增和刪除視窗,並設定視窗必要的顯示引數。FloatWindowManager.java
public class FloatWindowManager {
private static FloatWindow mFloatWindow; //懸浮窗View的例項
private static LayoutParams mWindowLayoutParams; //懸浮窗View的引數
private static WindowManager mWindowManager; //用於控制在螢幕上新增或移除懸浮窗
/**
* 建立一個懸浮窗
* 初始位置為螢幕的右部中間位置。
* @param context 必須為應用程式的Context.
*/
public static void createFloatWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
if (mFloatWindow == null) {
mFloatWindow = new FloatWindow(context);
if (mWindowLayoutParams == null) {
mWindowLayoutParams = new LayoutParams();
mWindowLayoutParams.type = LayoutParams.TYPE_PHONE;
mWindowLayoutParams.format = PixelFormat.RGBA_8888;
mWindowLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE;
mWindowLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mWindowLayoutParams.width = FloatWindow.viewWidth;
mWindowLayoutParams.height = FloatWindow.viewHeight;
mWindowLayoutParams.x = screenWidth;
mWindowLayoutParams.y = screenHeight / 2;
}
mFloatWindow.setParams(mWindowLayoutParams);
//最為關鍵的一步操作,該操作之後就可以在桌面上看到檢視
windowManager.addView(mFloatWindow, mWindowLayoutParams);
}
}
/**
* 將懸浮窗從螢幕上移除。
*
* @param context 必須為應用程式的Context.
*/
public static void removeFloatWindow(Context context) {
if (mFloatWindow != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(mFloatWindow);
mFloatWindow = null;
}
}
/**
* 更新懸浮窗文字顯示
* @param context
* @param tittle
* @param content
*/
public static void updateFloatShow(Context context,String tittle,String content) {
if (mFloatWindow != null) {
TextView percentView = (TextView) mFloatWindow.findViewById(R.id.percent);
percentView.setText("IM訊息");
}
}
public static boolean isWindowShowing() {
return mFloatWindow != null;
}
private static WindowManager getWindowManager(Context context) {
if (mWindowManager == null) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
}
具體測操作service類FloatWindowService.class
public class FloatWindowService extends Service {
private Timer mTimer;
private Handler mHandler = new Handler();
@Override
public void onCreate() {
super.onCreate();
if (mTimer == null) {
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mTimer == null) {
mTimer = new Timer();
mTimer.scheduleAtFixedRate(new RefreshTask(), 0, 1000);
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
mTimer.cancel();
mTimer = null;
super.onDestroy();
}
class RefreshTask extends TimerTask {
/**
* 當前介面是桌面,且沒有懸浮窗顯示,則建立懸浮窗
* 當前介面不是桌面,且有懸浮窗顯示,則移除懸浮窗
* 當前介面是桌面,且有懸浮窗顯示,設定懸浮窗內容
*/
@Override
public void run() {
if (WindowUtils.getInstance().isAtHome(getApplicationContext()) && !FloatWindowManager.isWindowShowing()) {
mHandler.post(new Runnable() {
@Override
public void run() {
FloatWindowManager.createFloatWindow(getApplicationContext());
}
});
} else if (!WindowUtils.getInstance().isAtHome(getApplicationContext()) && FloatWindowManager.isWindowShowing()) {
mHandler.post(new Runnable() {
@Override
public void run() {
FloatWindowManager.removeFloatWindow(getApplicationContext());
}
});
}
}
}
}
如果要開啟android懸浮視窗就啟用以上service即可。大致如下
Intent intent = new Intent(....this, FloatWindow.class);
VoiceBroadcastService.voiceUrl = voicePath;
startService(intent);
以上就是android桌面懸浮視窗的大致操作,希望能給大家帶來幫助。另外,這裡小小留一個懸念,就是FloatWindowService類中的isAtHome方法。這是判斷當前介面是否為桌面的方法,需要的朋友可以訪問Android判斷當前介面是否為桌面進行檢視。
另外需要特別注意的是WindowManager如果要呼叫.addView方法,需要為應用新增ACTION_MANAGE_OVERLAY_PERMISSION許可權。否則系統會閃退,請各位格外注意。具體的新增方式這裡不再多說了。