天天看點

(Android)桌面懸浮窗

懸浮窗一直都覺得是個高大上的東西,但是當你剖析之後,就會發現其實也挺簡單的,就是判斷目前界面是否是桌面,然後開啟一個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