仿360悬浮窗——进阶篇
上一章讲了仿360基础篇,这一章在基础上做一下功能的拓展,如果你需要做成像360一样的东西,可以在我源码基础上修改即可,当然如果你做launcher开发,也可以做成苹果手机那样的悬浮快捷按钮,还是老规矩,先贴图(两种效果):
这一次加了一个大的悬浮窗,用来处理复杂需求,我只是随便画了个圆形,你们可以做的更漂亮,功能更强大:
对于悬浮框的建立,我上一章已经见过,这里就不多做讲解,请参考:仿360悬浮窗基础版
因为有一大一小两个悬浮窗,我们不妨建立一个WindowManger来管理两个悬浮窗加载的状态,代码如下(注意SmallWindow和BigWindow创立时的差别):
public class FloatWindowManager {
/**
* 小悬浮窗
*/
private static FloatWindowSmall smallWindow;
/**
* 大悬浮窗
*/
private static FloatWindowBig bigWindow;
/**
* 小悬浮窗的params
*/
private static LayoutParams smallWindowParams;
/**
* 大悬浮窗的params
*/
private static LayoutParams bigWindowParams;
/**
* 用于控制在屏幕上添加或移除悬浮窗
*/
private static WindowManager mWindowManager;
/**
* 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。
*
* @param context 必须为应用程序的Context.
*/
public static void createSmallWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
DisplayMetrics dm = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(dm);
if (smallWindow == null) {
//这里必须先初始化小悬浮窗
smallWindow = new FloatWindowSmall(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 = FloatWindowSmall.viewWidth;
smallWindowParams.height = FloatWindowSmall.viewHeight;
smallWindowParams.x = dm.widthPixels;
smallWindowParams.y = dm.heightPixels / 2 - smallWindowParams.height / 2;
}
smallWindow.setParams(smallWindowParams);
windowManager.addView(smallWindow, smallWindowParams);
}
}
/**
* 将小悬浮窗从屏幕上移除。
*
* @param context 必须为应用程序的Context.
*/
public static void removeSmallWindow(Context context) {
if (smallWindow != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(smallWindow);
smallWindow = null;
}
}
/**
* 创建一个大悬浮窗。位置根据小悬浮窗确定。
*
* @param context 必须为应用程序的Context.
*/
public static void createBigWindow(Context context) {
WindowManager windowManager = getWindowManager(context);
DisplayMetrics dm = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(dm);
if (bigWindow == null) {
//这里必须先初始化大悬浮窗
bigWindow = new FloatWindowBig(context);
}
//参数是变动的,所以每一次开启都必须更新
bigWindowParams = new LayoutParams();
bigWindowParams.type = LayoutParams.TYPE_PHONE;
bigWindowParams.format = PixelFormat.RGBA_8888;
bigWindowParams.gravity = Gravity.LEFT | Gravity.TOP;
bigWindowParams.width = FloatWindowBig.viewWidth;
bigWindowParams.height = FloatWindowBig.viewHeight;
//如何不加这个,则会出现它一直霸占焦点,其他点击事件失效,切记
bigWindowParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE;
//这里是根据小悬浮窗的位置来确定大悬浮窗的位置,当然我们这里强制聚焦,所以屏蔽掉
// bigWindowParams.x = FloatWindowSmall.smallParams.x + FloatWindowSmall.viewWidth -
// bigWindowParams.width;
// bigWindowParams.y = FloatWindowSmall.smallParams.y + FloatWindowSmall.viewHeight / 2
// - bigWindowParams.height / 2;
//如果你想实现类似苹果手机的快捷操作悬浮窗效果,可以让他显示在中间位置
bigWindowParams.x = dm.widthPixels/2 - bigWindowParams.width/2;
bigWindowParams.y = dm.heightPixels/2 - bigWindowParams.height /2;
windowManager.addView(bigWindow, bigWindowParams);
}
/**
* 将大悬浮窗从屏幕上移除。
*
* @param context 必须为应用程序的Context.
*/
public static void removeBigWindow(Context context) {
if (bigWindow != null) {
WindowManager windowManager = getWindowManager(context);
windowManager.removeView(bigWindow);
bigWindow = null;
}
}
/**
* 更新小悬浮窗的TextView上的数据,显示内存使用的百分比。
*/
public static void updateUsedPercent() {
if (smallWindow != null) {
TextView percentView = (TextView) smallWindow.findViewById(R.id.percent);
percentView.setText(smallWindow.getUsedPercentValue());
}
}
/**
* 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
*
* @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
*/
public static boolean isWindowShowing() {
return smallWindow != null || bigWindow != null;
}
/**
* @param context 必须为getApplicationContext().
* @return WindowManager的实例
*/
private static WindowManager getWindowManager(Context context) {
if (mWindowManager == null) {
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
}
return mWindowManager;
}
}
至于悬浮窗,一大一小,小的上一章见过了,就不贴代码呢,我会上传整个源码,所以不要担心学不会,主要我不喜欢我的博客篇幅太长,还是讲思路和主要代码,大悬浮窗,代码很简单:
public class FloatWindowBig extends LinearLayout {
/**
* 记录大悬浮窗的宽度
*/
public static int viewWidth;
/**
* 记录大悬浮窗的高度
*/
public static int viewHeight;
public FloatWindowBig(final Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.float_window_big, this);
View view = findViewById(R.id.big_window_layout);
viewWidth = view.getLayoutParams().width;
viewHeight = view.getLayoutParams().height;
TextView close = (TextView) findViewById(R.id.close);
TextView back = (TextView) findViewById(R.id.back);
close.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 点击关闭悬浮窗的时候,移除所有悬浮窗,并停止Service
FloatWindowManager.removeBigWindow(context);
FloatWindowManager.removeSmallWindow(context);
Intent intent = new Intent(getContext(), FloatWindowService.class);
context.stopService(intent);
}
});
back.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 点击返回的时候,移除大悬浮窗,创建小悬浮窗
FloatWindowManager.removeBigWindow(context);
FloatWindowManager.createSmallWindow(context);
}
});
}
}
最后就是service部分,直接调用 FloatWindowManager的方法创建大小悬浮窗即可,这里需要做的是判断用户界面是否在laucher,当然如果模仿苹果手机的功能悬浮快捷按钮则不需要,代码如下:
<span style="white-space:pre"> </span>/**
* 判断当前界面是否是桌面
*/
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;
}
因为只是针对上一章做一下功能拓展,方便大家用于自己项目,所以没有太多思路可讲,效果图已经呈现,相信大家最需要的还是源码: 360悬浮窗——进阶篇