懸浮窗一直都覺得是個高大上的東西,但是當你剖析之後,就會發現其實也挺簡單的,就是判斷目前界面是否是桌面,然後開啟一個Service
懸浮窗涉及到WindowManager,通過調用其中的幾個方法:addView(添加懸浮窗)、removeView(移除懸浮窗)、updateViewLayout(更新懸浮窗);
首先就是申請權限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 使用SYSTEM_ALERT_WINDOW時必須要加 -->
<uses-permission android:name="android.permission.GET_TASKS"/>
然後就是布局檔案:開啟懸浮窗按鈕
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context=".MainActivity" >
<Button
android:id="@+id/start_float_window"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="start"/>
</RelativeLayout>
懸浮窗樣式:樣式可以根據自己需求設定的
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/small_window_layout"
android:layout_width="60dip"
android:layout_height="25dip"
android:background="@mipmap/bg_small">
<TextView
android:id="@+id/percent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:textColor="#ffffff"/>
</LinearLayout>
對懸浮窗做一個自定義的動态布局:
public class FloatWindowView extends LinearLayout {
public static int viewWidth;記錄懸浮窗的寬度
public static int viewHeight;記錄懸浮窗的高度
private static int statusBarHeight;記錄系統狀态欄的高度
private WindowManager windowManager; 用于更新懸浮窗的位置
private float xInScreen;記錄目前手指位置在螢幕上的橫坐标值
private float yInScreen;記錄目前手指位置在螢幕上的縱坐标值
private float xDownInScreen;記錄手指按下時在螢幕上的橫坐标的值
private float yDownInScreen;記錄手指按下時在螢幕上的縱坐标的值
private float xInView;//記錄手指按下時在小懸浮窗的View上的橫坐标的值
private float yInView;//記錄手指按下時在小懸浮窗的View上的縱坐标的值
public FloatWindowView(Context context) {
super(context);
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
LayoutInflater.from(context).inflate(R.layout.float_window, this);
View view = findViewById(R.id.window_layout);
viewWidth = view.getLayoutParams().width;
viewHeight = view.getLayoutParams().height;
//這裡可以設定懸浮窗裡面展示的内容
}
@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;
default:
break;
}
return true;
}
/**
* 更新小懸浮窗在螢幕中的位置。
*/
private void updateViewPosition() {
mParams.x = (int) (xInScreen - xInView);
mParams.y = (int) (yInScreen - yInView);
windowManager.updateViewLayout(this, mParams);
}
/**
* 用于擷取狀态欄的高度。
* @return 傳回狀态欄高度的像素值。
*/
private int getStatusBarHeight() {
if (statusBarHeight == ) {
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:
建立懸浮窗,并設定懸浮窗顯示的初始位置
public static void createSmallWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
int screenWidth = windowManager.getDefaultDisplay().getWidth();
int screenHeight = windowManager.getDefaultDisplay().getHeight();
if (smallWindow == null) {
smallWindow = new FloatWindowView(context);
if (smallWindowParams == null) {
smallWindowParams = new LayoutParams();
smallWindowParams.type = LayoutParams.TYPE_PHONE;
smallWindowParams.format = PixelFormat.RGBA_8888;
smallWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;
smallWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
smallWindowParams.width = FloatWindowView.viewWidth;
smallWindowParams.height = FloatWindowView.viewHeight;
smallWindowParams.x = screenWidth;
smallWindowParams.y = screenHeight / ;
}
smallWindow.setParams(smallWindowParams);
windowManager.addView(smallWindow, smallWindowParams);
}
}
public static void removeWindow(Context context) {
if (smallWindow != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(smallWindow);
smallWindow = null;
}
}
public static void updateUsedPercent(Context context) {
if (smallWindow != null) {
//通過findviewbyid,重新設定懸浮窗的資料顯示
}
}
public static boolean isWindowShowing() {
return smallWindow != null;//判斷桌面是否有懸浮窗
}
别忘了把這個寫上:
/**
* 如果WindowManager還未建立,則建立一個新的WindowManager傳回。否則傳回目前已建立的WindowManager。
* @param context 必須為應用程式的Context.
* @return WindowManager的執行個體,用于控制在螢幕上添加或移除懸浮窗。
*/
private static WindowManager getWindowManager(Context context) {
if (mWindowManager == null) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
/**
* 如果ActivityManager還未建立,則建立一個新的ActivityManager傳回。否則傳回目前已建立的ActivityManager。
* @param context 可傳入應用程式上下文。
* @return ActivityManager的執行個體,用于擷取手機可用記憶體。
*/
private static ActivityManager getActivityManager(Context context) {
if (mActivityManager == null) {
mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
}
return mActivityManager;
}
建立一個懸浮窗的背景服務,并繼承Service:
1、判斷目前界面是否是桌面,隻有在桌面的時候才會開啟懸浮窗:
/**
* 判斷目前界面是否是桌面
*/
private boolean isHome() {
ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> rti = mActivityManager.getRunningTasks();
return getHomes().contains(rti.get().topActivity.getPackageName());
}
/**
* 獲得屬于桌面的應用的應用包名稱
* @return 傳回包含所有包名的字元串清單
*/
private List<String> getHomes() {
List<String> names = new ArrayList<String>();
PackageManager packageManager = this.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo ri : resolveInfo) {
names.add(ri.activityInfo.packageName);
}
return names;
}
2、開啟一個線程Handler去建立和移除懸浮窗
線上程中執行判斷:
// 目前界面是桌面,且沒有懸浮窗顯示,則建立懸浮窗。
if (isHome() && !MyWindowManager.isWindowShowing()) {
handler.post(new Runnable() {
@Override
public void run() {
MyWindowManager.createWindow(getApplicationContext());
}
});
}
// 目前界面不是桌面,且有懸浮窗顯示,則移除懸浮窗。
else if (!isHome() && MyWindowManager.isWindowShowing()) {
handler.post(new Runnable() {
@Override
public void run() {
MyWindowManager.removeSmallWindow(getApplicationContext());
MyWindowManager.removeBigWindow(getApplicationContext());
}
});
}
然後在服務中開啟線程去執行,
由于代碼偏多,是以隻展示了一部分,說的不夠全面,還望了解,不過大緻意思就是這樣的
完整資源:http://download.csdn.net/detail/qq_36159785/9884890