悬浮窗一直都觉得是个高大上的东西,但是当你剖析之后,就会发现其实也挺简单的,就是判断当前界面是否是桌面,然后开启一个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 == 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:
创建悬浮窗,并设置悬浮窗显示的初始位置
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 / 2;
}
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(1);
return getHomes().contains(rti.get(0).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