悬浮框基本的实现方式有两种: 1、 在一个页面内,可以用FrameLayout 或者RelativeLayout。FrameLayout 中view是在左上角堆叠的,也就是说是z-order的,所以可以页面的基布局是FrameLayout,然后在上面放一个view,并且更新view的translationX ,translationY来改变位置。 RelativeLayout 可以用alignParent属性确定在父视图的位置。不过位置不可变。 如果这种方式显示全应用悬浮,那么就要每个页面都加,显然有点工作量。 2、 windowmanager 添加视图。这种方式比较灵活,不同activity都可以悬浮,甚至可以作为系统图层,放在应用之上。
这里使用第二种方式
核心思想就是在windowmanager中添加view、移除view,监听触摸事件更新view。windowmanager具体使用这里不详细讲。这里只简单的继承了imageView,当然可以通过继承如linearLayout等布局,添加自己定义的view上去。show的时候添加view,dismiss的时候移除view。
很尴尬的是,上面的方法显示出来的悬浮框,在应用按了home键或者退出到桌面的时候还在。但是很多场景只想它在自己的应用里悬浮,甚至只在指定的页面悬浮。
方法网上有不少方法可以参考,我是这么做的: 用一个helper维护FloatView 的实例,检测当前activity是不是位于前台,如果位于前台,windowmanager添加视图,位于后台移除视图;同样的,检查当前activity是否在过滤列表,在就不显示。这样就可以达到在应用内的指定页面悬浮。 代码如下:
public class FloatViewHelper { private static final String TAG = "FloatViewHelper"; private static FloatView mFloatView; private static List<String> notShowList = new ArrayList(); static { //添加不需要显示的页面 notShowList.add("MainActivity"); } public static void showFloatView(final Context context){ if(!CommonUtils.isAppOnForeground(context)||CommonUtils.isTargetRunningForeground(context,notShowList)){ return; } if(mFloatView == null){ mFloatView = new FloatView(context.getApplicationContext()); } mFloatView.show(); } public static void removeFloatView(Context context){ if(CommonUtils.isAppOnForeground(context)&&CommonUtils.isTargetRunningForeground(context,notShowList)){ return; } if(mFloatView ==null||mFloatView.getWindowToken()==null){ return; } mFloatView.dismiss(); } public static void addFilterActivities(List<String> activityNames){ notShowList.addAll(activityNames); } }isAppOnForeground 方法贴出来:
/** * 程序是否在前台运行 * * @return */ public static boolean isAppOnForeground(Context context) { // Returns a list of application processes that are running on the // device ActivityManager activityManager = (ActivityManager) context .getApplicationContext() .getSystemService(Context.ACTIVITY_SERVICE); String packageName = context.getApplicationContext().getPackageName(); List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager .getRunningAppProcesses(); if (appProcesses == null) return false; for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { // The name of the process that this object is associated with. if (appProcess.processName.equals(packageName) && appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { return true; } } return false; }检查当前页面是否在过滤列表:
public static boolean isTargetRunningForeground(Context context,List<String> targetActivityNames) { String topActivityName = ((Activity)context).getClass().getSimpleName(); if (!TextUtils.isEmpty(topActivityName) && targetActivityNames.contains(topActivityName)) { return true; } return false; }然后在BaseActivity的onStart()中使用FloatViewHelper.showFloatView(this);onPause()中使用FloatViewHelper.removeFloatView(this);之后所有activity都继承这个BaseActivity。
这个要说到activity的生命周期。正常情况下,从activity A跳转到activity B,经历的生命周期是: A:onCreate()–>onStart()–> onResume()–>onPause()->B:onCreate()–>onStart()–>onResume()–>A :onStop()–>onDestory()。 那我们要实现的就是,前一个页面跳转到后一个页面的时候,前一个页面remove,后一个页面show,所以从周期来讲,应该在onResum()方法中show,在onPause()方法中remove。
到此就大功告成啦!
在android7.0以上(自测7.1.1),因为对窗口权限做了限制,所以要做兼容处理: FloatView.java
if(Build.VERSION.SDK_INT > 24) { wlp.type = WindowManager.LayoutParams.TYPE_PHONE; }else { wlp.type = WindowManager.LayoutParams.TYPE_TOAST; }然后在使用之前要在AndroidManifest.xml中添加android.permission.SYSTEM_ALERT_WINDOW 这个权限,然后在使用之前,判断有没有权限:
private static boolean hasPermission(Context context){ if( Build.VERSION.SDK_INT > 24 ) { return Settings.canDrawOverlays(context); } return true; }注意,试过checkPermission()等方法,发现并不起作用,要使用Settings.canDrawOverlays(context)判断。