Android悬浮窗效果
qq_21467035 人气:0一、权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
二、悬浮窗其实就是 WindowManager.addView(view,layoutParams),直接上代码
1、单例创建FloatWindowManager
/** 悬浮Manager */ public class FloatWindowManager { private volatile static FloatWindowManager mInstance; private WindowManager mWindowManager; private Context mContext; private WindowManager.LayoutParams mLayoutParams; private int layoutY; private int layoutX; private ValueAnimator animator; private TextView textView; public static synchronized FloatWindowManager getInstance() { if (mInstance == null) { synchronized (FloatWindowManager.class) { if (mInstance == null) { mInstance = new FloatWindowManager(); } } } return mInstance; } public FloatWindowManager initManager(Context context) { mContext = context; mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); showWindow(); return this; } /** * 是否有悬浮框权限 * * @return */ public boolean requestPermission(Context context) { return SettingsCompat.canDrawOverlays(context, true, false); } /** * 加载 悬浮窗 没有内容 */ private synchronized void showWindow() { textView = new TextView(mContext); textView.setText("此为悬浮窗口View"); textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp2px(mContext, 15)); mLayoutParams = new WindowManager.LayoutParams(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; } mLayoutParams.format = PixelFormat.RGBA_8888; //窗口透明 mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP; //窗口位置 mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); layoutY = displayMetrics.heightPixels / 2; layoutX = displayMetrics.widthPixels - textView.getMeasuredWidth(); mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; mLayoutParams.x = layoutX; mLayoutParams.y = layoutY; setListener(); } public void showFloatWindow(){ mWindowManager.addView(textView, mLayoutParams); } /** * 设置 悬浮窗 view 滑动事件 */ private void setListener() { if (textView != null) { textView.setOnTouchListener(new View.OnTouchListener() { private int moveX; //动画平移距离 int startX, startY; //起始点 boolean isMove; //是否在移动 long startTime; int finalMoveX; //最后通过动画将mView的X轴坐标移动到finalMoveX boolean downMove = false; @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: startX = (int) event.getX(); startY = (int) event.getY(); startTime = System.currentTimeMillis(); isMove = false; downMove = false; return false; case MotionEvent.ACTION_MOVE: //当移动距离大于2时候,刷新界面。 if (Math.abs(startX - event.getX()) > 2 || Math.abs(startY - event.getY()) > 2) { downMove = true; mLayoutParams.x = (int) (event.getRawX() - startX); mLayoutParams.y = (int) (event.getRawY() - startY); updateViewLayout(); //更新mView 的位置 } return true; case MotionEvent.ACTION_UP: long curTime = System.currentTimeMillis(); isMove = curTime - startTime > 100; if (isMove){ //判断mView是在Window中的位置,以中间为界 if (mLayoutParams.x + textView.getMeasuredWidth() / 2 >= mWindowManager.getDefaultDisplay().getWidth() / 2) { finalMoveX = mWindowManager.getDefaultDisplay().getWidth() - textView.getMeasuredWidth(); } else { finalMoveX = 0; } //使用动画移动mView animator = ValueAnimator.ofInt(mLayoutParams.x, finalMoveX).setDuration(Math.abs(mLayoutParams.x - finalMoveX)); animator.addUpdateListener((ValueAnimator animation) -> { if (animation != null) { moveX = (int) animation.getAnimatedValue(); mLayoutParams.x = (int) animation.getAnimatedValue(); updateViewLayout(); } }); animator.start(); } return isMove; } return false; } }); } } /** * 刷新 circle view 位置 */ private void updateViewLayout() { if (null != textView && null != mLayoutParams && mWindowManager != null) { try { mWindowManager.updateViewLayout(textView, mLayoutParams); } catch (Exception e) { e.printStackTrace(); } } } /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) */ public static int dp2px(Context context, float dpValue) { if (context != null) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } return (int) dpValue; }
2、SettingsCompat 动态权限判断(适配大部分厂商)
public class SettingsCompat { private static final int OP_WRITE_SETTINGS = 23; private static final int OP_SYSTEM_ALERT_WINDOW = 24; /** * 检查悬浮窗权限 当没有权限,跳转到权限设置界面 * * @param context 上下文 * @param isShowDialog 没有权限,是否弹框提示跳转到权限设置界面 * @param isShowPermission 是否跳转权限开启界面 * @return true 有权限 false 没有权限(跳转权限界面、权限失败 提示用户手动设置权限) * @by 腾讯云直播 悬浮框判断逻辑 */ public static boolean canDrawOverlays(Context context, boolean isShowDialog, boolean isShowPermission) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (!Settings.canDrawOverlays(context)) { if (isShowDialog) { //去授权 SettingsCompat.manageDrawOverlays(context); } else if (isShowPermission) { manageDrawOverlays(context); } return false; } return true; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { if (checkOp(context, OP_SYSTEM_ALERT_WINDOW)) { return true; } else { if (isShowPermission) startFloatWindowPermissionErrorToast(context); return false; } } else { return true; } } /** * 打开 悬浮窗 授权界面 * * @param context */ public static void manageDrawOverlays(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { try { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + context.getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } catch (Exception e) { e.printStackTrace(); startFloatWindowPermissionErrorToast(context); } } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { if (!manageDrawOverlaysForRom(context)) { startFloatWindowPermissionErrorToast(context); } } } /** * 权限设置 失败提示。 * * @param context */ public static void startFloatWindowPermissionErrorToast(Context context) { if (context != null) Toast.makeText(context, "进入设置页面失败,请手动开启悬浮窗权限", Toast.LENGTH_SHORT).show(); } private static boolean manageDrawOverlaysForRom(Context context) { if (RomUtil.isMiui()) { return manageDrawOverlaysForMiui(context); } if (RomUtil.isEmui()) { return manageDrawOverlaysForEmui(context); } if (RomUtil.isFlyme()) { return manageDrawOverlaysForFlyme(context); } if (RomUtil.isOppo()) { return manageDrawOverlaysForOppo(context); } if (RomUtil.isVivo()) { return manageDrawOverlaysForVivo(context); } if (RomUtil.isQiku()) { return manageDrawOverlaysForQihu(context); } if (RomUtil.isSmartisan()) { return manageDrawOverlaysForSmartisan(context); } return false; } private static boolean checkOp(Context context, int op) { AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); try { Method method = AppOpsManager.class.getDeclaredMethod("checkOp", int.class, int.class, String.class); return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName()); } catch (Exception e) { } return false; } // 可设置Android 4.3/4.4的授权状态 private static boolean setMode(Context context, int op, boolean allowed) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return false; } AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); try { Method method = AppOpsManager.class.getDeclaredMethod("setMode", int.class, int.class, String.class, int.class); method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName(), allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); return true; } catch (Exception e) { } return false; } /** * 跳转界面 * * @param context * @param intent * @return */ private static boolean startSafely(Context context, Intent intent) { List<ResolveInfo> resolveInfos = null; try { resolveInfos = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); if (resolveInfos != null && resolveInfos.size() > 0) { intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); return true; } } catch (Exception e) { e.printStackTrace(); } return false; } // 小米 private static boolean manageDrawOverlaysForMiui(Context context) { Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR"); intent.putExtra("extra_pkgname", context.getPackageName()); intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); if (startSafely(context, intent)) { return true; } intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity"); if (startSafely(context, intent)) { return true; } // miui v5 的支持的android版本最高 4.x // http://www.romzj.com/list/search?keyword=MIUI%20V5#search_result if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { Intent intent1 = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent1.setData(Uri.fromParts("package", context.getPackageName(), null)); return startSafely(context, intent1); } return false; } private final static String HUAWEI_PACKAGE = "com.huawei.systemmanager"; // 华为 private static boolean manageDrawOverlaysForEmui(Context context) { Intent intent = new Intent(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { intent.setClassName(HUAWEI_PACKAGE, "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity"); if (startSafely(context, intent)) { return true; } } // Huawei Honor P6|4.4.4|3.0 intent.setClassName(HUAWEI_PACKAGE, "com.huawei.notificationmanager.ui.NotificationManagmentActivity"); intent.putExtra("showTabsNumber", 1); if (startSafely(context, intent)) { return true; } intent.setClassName(HUAWEI_PACKAGE, "com.huawei.permissionmanager.ui.MainActivity"); if (startSafely(context, intent)) { return true; } return false; } // VIVO private static boolean manageDrawOverlaysForVivo(Context context) { // 不支持直接到达悬浮窗设置页,只能到 i管家 首页 Intent intent = new Intent("com.iqoo.secure"); intent.setClassName("com.iqoo.secure", "com.iqoo.secure.MainActivity"); // com.iqoo.secure.ui.phoneoptimize.SoftwareManagerActivity // com.iqoo.secure.ui.phoneoptimize.FloatWindowManager return startSafely(context, intent); } // OPPO private static boolean manageDrawOverlaysForOppo(Context context) { Intent intent = new Intent(); intent.putExtra("packageName", context.getPackageName()); // OPPO A53|5.1.1|2.1 intent.setAction("com.oppo.safe"); intent.setClassName("com.oppo.safe", "com.oppo.safe.permission.floatwindow.FloatWindowListActivity"); if (startSafely(context, intent)) { return true; } // OPPO R7s|4.4.4|2.1 intent.setAction("com.color.safecenter"); intent.setClassName("com.color.safecenter", "com.color.safecenter.permission.floatwindow.FloatWindowListActivity"); if (startSafely(context, intent)) { return true; } intent.setAction("com.coloros.safecenter"); intent.setClassName("com.coloros.safecenter", "com.coloros.safecenter.sysfloatwindow.FloatWindowListActivity"); return startSafely(context, intent); } // 魅族 private static boolean manageDrawOverlaysForFlyme(Context context) { Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity"); intent.putExtra("packageName", context.getPackageName()); return startSafely(context, intent); } // 360 private static boolean manageDrawOverlaysForQihu(Context context) { Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.Settings$OverlaySettingsActivity"); if (startSafely(context, intent)) { return true; } intent.setClassName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity"); return startSafely(context, intent); } // 锤子 private static boolean manageDrawOverlaysForSmartisan(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return false; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // 锤子 坚果|5.1.1|2.5.3 Intent intent = new Intent("com.smartisanos.security.action.SWITCHED_PERMISSIONS_NEW"); intent.setClassName("com.smartisanos.security", "com.smartisanos.security.SwitchedPermissions"); intent.putExtra("index", 17); // 不同版本会不一样 return startSafely(context, intent); } else { // 锤子 坚果|4.4.4|2.1.2 Intent intent = new Intent("com.smartisanos.security.action.SWITCHED_PERMISSIONS"); intent.setClassName("com.smartisanos.security", "com.smartisanos.security.SwitchedPermissions"); intent.putExtra("permission", new String[]{Manifest.permission.SYSTEM_ALERT_WINDOW}); // Intent intent = new Intent("com.smartisanos.security.action.MAIN"); // intent.setClassName("com.smartisanos.security", "com.smartisanos.security.MainActivity"); return startSafely(context, intent); } } }
3、厂商 RomUtil
public class RomUtil { private static final String TAG = "RomUtil"; public static final String ROM_MIUI = "MIUI"; public static final String ROM_EMUI = "EMUI"; public static final String ROM_FLYME = "FLYME"; public static final String ROM_OPPO = "OPPO"; public static final String ROM_SMARTISAN = "SMARTISAN"; public static final String ROM_VIVO = "VIVO"; public static final String ROM_QIKU = "QIKU"; public static final String ROM_LENOVO = "LENOVO"; public static final String ROM_SAMSUNG = "SAMSUNG"; private static final String KEY_VERSION_MIUI = "ro.miui.ui.version.name"; private static final String KEY_VERSION_EMUI = "ro.build.version.emui"; private static final String KEY_VERSION_OPPO = "ro.build.version.opporom"; private static final String KEY_VERSION_SMARTISAN = "ro.smartisan.version"; private static final String KEY_VERSION_VIVO = "ro.vivo.os.version"; private static final String KEY_VERSION_GIONEE = "ro.gn.sv.version"; private static final String KEY_VERSION_LENOVO = "ro.lenovo.lvp.version"; private static final String KEY_VERSION_FLYME = "ro.build.display.id"; private static final String KEY_EMUI_VERSION_CODE = "ro.build.hw_emui_api_level"; private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; private static final String KEY_MIUI_HANDY_MODE_SF = "ro.miui.has_handy_mode_sf"; private static final String KEY_MIUI_REAL_BLUR = "ro.miui.has_real_blur"; private static final String KEY_FLYME_PUBLISHED = "ro.flyme.published"; private static final String KEY_FLYME_FLYME = "ro.meizu.setupwizard.flyme"; private static final String KEY_FLYME_ICON_FALG = "persist.sys.use.flyme.icon"; private static final String KEY_FLYME_SETUP_FALG = "ro.meizu.setupwizard.flyme"; private static final String KEY_FLYME_PUBLISH_FALG = "ro.flyme.published"; private static final String KEY_VIVO_OS_NAME = "ro.vivo.os.name"; private static final String KEY_VIVO_OS_VERSION = "ro.vivo.os.version"; private static final String KEY_VIVO_ROM_VERSION = "ro.vivo.rom.version"; public static boolean isEmui() { return check(ROM_EMUI); } public static boolean isMiui() { return check(ROM_MIUI); } public static boolean isVivo() { return check(ROM_VIVO); } public static boolean isOppo() { return check(ROM_OPPO); } public static boolean isFlyme() { return check(ROM_FLYME); } public static boolean isQiku() { return check(ROM_QIKU) || check("360"); } public static boolean isSmartisan() { return check(ROM_SMARTISAN); } private static String sName; public static String getName() { if (sName == null) { check(""); } return sName; } private static String sVersion; public static String getVersion() { if (sVersion == null) { check(""); } return sVersion; } public static boolean check(String rom) { if (sName != null) { return sName.equals(rom); } if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_MIUI))) { sName = ROM_MIUI; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_EMUI))) { sName = ROM_EMUI; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_OPPO))) { sName = ROM_OPPO; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_VIVO))) { sName = ROM_VIVO; } else if (!TextUtils.isEmpty(sVersion = getProp(KEY_VERSION_SMARTISAN))) { sName = ROM_SMARTISAN; } else { sVersion = Build.DISPLAY; if (sVersion.toUpperCase().contains(ROM_FLYME)) { sName = ROM_FLYME; } else { sVersion = Build.UNKNOWN; sName = Build.MANUFACTURER.toUpperCase(); } } return sName.equals(rom); } public static String getProp(String name) { String line = null; BufferedReader input = null; try { Process p = Runtime.getRuntime().exec("getprop " + name); input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); line = input.readLine(); input.close(); } catch (IOException ex) { Log.e(TAG, "Unable to read prop " + name, ex); return null; } finally { if (input != null) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } } return line; } }
加载全部内容