Android 悬浮窗口(及解决6.0以上无法显示问题)
2017-07-26 11:23
459 查看
思路实现
通过WindowManager添加一个View,创建一个系统顶级的窗口,实现悬浮窗口的效果。
本篇思路,来源于郭霖大神的悬浮窗口教程。
常用API:
addView():添加一个View对象
updateViewLayout():更新指定的View对象
removeView():移除一个View对象
红米手机需要先开启悬浮权限:显示悬浮窗–>允许。
录制效果如下:
AndroidStudio 自带的模拟器,其API 24。
运行结果:
在输出台上提示以下错误:
查看SYSTEM_ALERT_WINDOW权限,可知:
若是运用程序的目标API在23及其以上,程序需要通过权限管理界面,开启授权。程序发送ACTION_MANAGE_OVERLAY_PERMISSION的动作,使用Settings.canDrawOverlays()来检查是否授权。
郭大大的教程:http://blog.csdn.net/guolin_blog/article/details/8689140/
通过WindowManager添加一个View,创建一个系统顶级的窗口,实现悬浮窗口的效果。
本篇思路,来源于郭霖大神的悬浮窗口教程。
大致介绍WindowManager 类
创建的对象:Context.getSystemService(Context.WINDOW_SERVICE)
常用API:
addView():添加一个View对象
updateViewLayout():更新指定的View对象
removeView():移除一个View对象
使用Kotlin编程,实战开发
1. 编写弹窗中布局文件,item_message.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:id="@+id/suspension_window_layout" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:text="系统悬浮弹窗" android:textColor="@android:color/white" android:textSize="18sp" android:padding="10dp" android:background="@color/colorPrimary" android:layout_height="wrap_content" /> </LinearLayout>
2. 编写悬浮弹窗的View:
定义一个布局,将对应的item_message.xml绑定上,重写onTouche()悬浮弹窗,实现自动拖动,点击关闭的效果。class SuspensionWindowLayout(context: Context) : RelativeLayout(context) { /** * statusbar系统状态栏的高度 */ var statusbarHeight = 0 /** * 窗口管理器 */ val windowManager: WindowManager init { windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager var childView= View.inflate(context, R.layout.item_message,this) widget_width = childView.suspension_window_layout.layoutParams.width widget_height = childView.suspension_window_layout.layoutParams.height } /** * 按下屏幕时手指在x,y轴上的坐标 */ var down_x = 0.0f var down_y = down_x /** * 移动时候的手指在x,y轴上的坐标 */ var move_x = down_x var move_y = down_x /** * 按下屏幕时候,控件在x,y轴位置 */ var widget_x = down_x var widget_y = down_x /** * 重写处理拖动事件 */ override fun onTouchEvent(event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN -> { // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度 widget_x = event.x widget_y = event.x //没有移动,down->up,点击事件 down_x = event.rawX down_y = event.rawY - getStatusBarHeight() move_x = event.rawX move_y = event.rawY - getStatusBarHeight() } MotionEvent.ACTION_MOVE -> { // 手指移动的时候更新小悬浮窗的位置 move_x = event.rawX move_y = event.rawY - getStatusBarHeight() updateWidgetPostion() } MotionEvent.ACTION_UP -> {// //坐标没有改变,是点击动作 if (move_x == down_x && move_y == down_y) { SuspensionWindowManagerUtils.removeSuspensionWindow(context) } } else -> { } } return true } /** * 更新控件位置,在x,y轴的的位置 */ fun updateWidgetPostion() { var layoutParams = SuspensionWindowManagerUtils.getWidgetLayoutParams() layoutParams!!.x = (move_x - widget_x).toInt() layoutParams!!.y = (move_y - widget_y).toInt() windowManager.updateViewLayout(this, layoutParams) } /** * 获取系统状态栏,返回状态栏高度的像素值 */ fun getStatusBarHeight(): Int { if (statusbarHeight == 0) { statusbarHeight = resources.getDimensionPixelSize(ViewUtils.getStatusBarHeight()) } return statusbarHeight } companion object { var widget_width = 0 var widget_height = 0 } }
一个工具类:
public class ViewUtils { /** * 反射获取状态栏高度 * @return */ public static int getStatusBarHeight(){ int x=0; try { Class<?> c = Class.forName("com.android.internal.R$dimen"); Object o = c.newInstance(); Field field = c.getField("status_bar_height"); x = (Integer) field.get(o); } catch (Exception e) { e.printStackTrace(); } return x; } }
3. 编写WindowManager工具类:
一些列,检查弹窗,开启弹窗,关闭弹窗的操作封装到该类中。class SuspensionWindowManagerUtils { companion object { var windowManager: WindowManager?=null var layoutParams: WindowManager.LayoutParams?=null var suspensionWindowWidget: SuspensionWindowLayout? = null /** * 创建悬浮窗口 */ @JvmStatic fun createSuspensionWindow(context: Context) { if (suspensionWindowWidget==null){ suspensionWindowWidget= SuspensionWindowLayout(context) } getWindowManager(context)!!.addView(suspensionWindowWidget, getWidgetLayoutParams()) } /** * 移除悬浮窗口 */ fun removeSuspensionWindow(context: Context) { if (suspensionWindowWidget != null) { getWindowManager(context)!!.removeView(suspensionWindowWidget) suspensionWindowWidget = null } } /** * 悬浮窗口是否已经打开 */ fun windowIsOpen():Boolean{ if (suspensionWindowWidget!=null) return true else return false } /** * 获取悬浮窗口的布局参数 */ fun getWidgetLayoutParams(): WindowManager.LayoutParams? { if (layoutParams == null) { layoutParams = WindowManager.LayoutParams() layoutParams!!.type = WindowManager.LayoutParams.TYPE_PHONE layoutParams!!.format = PixelFormat.RGBA_8888 layoutParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE layoutParams!!.gravity = Gravity.LEFT or Gravity.TOP layoutParams!!.x = windowManager!!.defaultDisplay.width layoutParams!!.y =0 layoutParams!!.width = SuspensionWindowLayout.widget_width layoutParams!!.height = SuspensionWindowLayout.widget_height } return layoutParams } /** * 获取窗口管理器 */ fun getWindowManager(context: Context): WindowManager ?{ if (windowManager == null) { windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager } return windowManager } } }
4. 开启一个悬浮窗口:
先进行判断,若是悬浮弹窗为未开启,则进行开启。import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //点击开启悬浮窗口 main_open_window.setOnClickListener { requestPermission() } } /** * 开启悬浮弹窗 */ fun openSuspensionWindow(){ //未开启窗口,则开启 if (!SuspensionWindowManagerUtils.windowIsOpen()) { SuspensionWindowManagerUtils.createSuspensionWindow(applicationContext) } } }
5. 在AndroidManifest.xml中添加系统弹窗权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
6. Android 5.1及其以下系统,运行效果:
在模拟器上运行无问题,但是在红米手机上出现问题。红米手机需要先开启悬浮权限:显示悬浮窗–>允许。
录制效果如下:
授权 SYSTEM_ALERT_WINDOW Permission 在 android 6.0 及其以上版本的系统
运行设备:AndroidStudio 自带的模拟器,其API 24。
运行结果:
在输出台上提示以下错误:
Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@b9261f -- permission denied for window type 2002 at android.view.ViewRootImpl.setView(ViewRootImpl.java:702) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
查看SYSTEM_ALERT_WINDOW权限,可知:
若是运用程序的目标API在23及其以上,程序需要通过权限管理界面,开启授权。程序发送ACTION_MANAGE_OVERLAY_PERMISSION的动作,使用Settings.canDrawOverlays()来检查是否授权。
解决方式:
1. 检查权限:
使用Settings.canDrawOverlays()来检查是否授权。
/** * 当目标版本大于23时候,检查权限 */ fun checkPermission():Boolean{ if (Build.VERSION.SDK_INT>=23) return Settings.canDrawOverlays(this) else return true }
2. 用户授权:
发送ACTION_MANAGE_OVERLAY_PERMISSION,开启权限授权界面,用户授权,允许悬浮在运用程序之上。
/** * 申请权限的状态code */ var request_code=1 /** * 开启权限管理界面,授权。 */ fun requestPermission(){ var intent=Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName")) startActivityForResult(intent,request_code) }
3. 响应授权结果:
使用Settings.canDrawOverlays()来检查授权结果,用户在管理界面是否授权。
/** * 回调申请结果 */ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when(requestCode) { request_code -> { if (checkPermission()) { //用户授权成功 openSuspensionWindow() } else { //用户拒绝授权 Toast.makeText(application, "弹窗权限被拒绝", Toast.LENGTH_SHORT).show() } } } super.onActivityResult(requestCode, resultCode, data) }
4. 在Android6.0及其以上,运行效果:
在7.0系统模拟器上,API24运行项目,效果如下项目代码:https://github.com/13767004362/SuspensionWindowDemo
资源参考:郭大大的教程:http://blog.csdn.net/guolin_blog/article/details/8689140/
相关文章推荐
- Android 6.0以上 解决recyclerview 在 scrollview 中不能全部显示,高度不正常的问题
- Android真机调试时突然无法显示Logcat信息问题解决参考
- Android:ListView底部footview无法显示问题解决
- android程序在真机上运行无法显示所有LogCat信息问题解决
- 解决eclipse中Android SDK Manager更新慢和部分无法显示System Image镜像问题
- 解决不同版本的eclipse或sdk所导致的android layout xml无法显示的问题
- android窗口泄漏,isInEditMode解决可视化编辑器无法识别自定义控件的问题
- 彻底解决android拍照后无法显示的问题
- Linphone android 最新版编译(解决android5.0以上无法运行问题)
- Visual Studio 2008项目中WinForm窗口图标显示为类图标,仅仅能打开代码而无法打开视图问题解决
- 解决Android调用系统相机拍照后相片无法在相册中显示问题
- 解决android中googlemap无法显示问题
- android listview嵌套viewpager,viewpager嵌套gridview,解决内嵌无法显示以及时间冲突的问题
- 解决Android 5.1系统以上通知状态栏小图标只显示白色问题
- VS2010使用C++/CLI编程窗口无法显示问题解决方法
- 解决Android 4.0以上版本中OptionsMenu菜单不显示ICON图标的问题
- Android 6.0 解决recyclerview 在 scrollview 中不能全部显示,高度不正常的问题。
- 解决IE新开窗口无法显示内容的问题
- 《ArcGIS Runtime SDK for Android开发笔记》——问题集:如何解决ArcGIS Runtime SDK for Android中文标注无法显示的问题(转载)
- [Android]浮层视频效果,在另外一个Window使用SurfaceView无法正常显示的问题排查与解决