Android 弹窗浅谈

我们知道 Android 弹窗中,有一类弹窗会在应用之外也显示,这是因为他被申明成了系统弹窗,除此之外还有2类弹窗分别是:子弹窗应用弹窗

应用弹窗:就是我们常规使用的 Dialog 之类弹窗,依赖于应用的 Activity;子弹窗:依赖于父窗口,比如 PopupWindow;系统弹窗:比如状态栏、Toast等,本文所讲的系统悬浮窗就是系统弹窗。

系统悬浮窗具体实现

权限申请

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />

代码设计

下面的包结构截图,简单展示了实现系统悬浮窗的代码结构,更复杂的业务需要可在此基础上进行扩展。

FloatWindowService:系统悬浮窗在此 Service 中弹出;

FloatWindowManager:系统悬浮窗管理类;

FloatLayout:系统悬浮窗布局;

HomeKeyObserverReceiver:

监听 Home 键;

FloatWindowUtils:系统悬浮窗工具类。

具体实现

FloatWindowService 类

class FloatWindowService : Service() {
 
    private val TAG = FloatWindowService::class.java.simpleName
    private var mFloatWindowManager: FloatWindowManager? = null
    private var mHomeKeyObserverReceiver: HomeKeyObserverReceiver? = null
 
    override fun onCreate() {
        TLogUtils.i(TAG, "onCreate: ")
        mFloatWindowManager = FloatWindowManager(applicationContext)
        mHomeKeyObserverReceiver = HomeKeyObserverReceiver()
 
        registerReceiver(mHomeKeyObserverReceiver, IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
        mFloatWindowManager!!.createWindow()
    }
 
    override fun onBind(intent: Intent?): IBinder? {
        return null
    }
 
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        return START_NOT_STICKY
    }
 
    override fun onDestroy() {
        TLogUtils.i(TAG, "onDestroy: ")
        mFloatWindowManager?.removeWindow()
        if (mHomeKeyObserverReceiver != null) {
            unregisterReceiver(mHomeKeyObserverReceiver)
        }
    }
 
}

FloatWindowManager 类

包括系统悬浮窗的创建、显示、销毁(以及更新)。

 
public void addView(View view, ViewGroup.LayoutParams params); // 添加 View 到 Window
public void updateViewLayout(View view, ViewGroup.LayoutParams params); //更新 View 在 Window 中的位置
public void removeView(View view); //删除 View

FloatWindowManager 类代码

class FloatWindowManager constructor(context: Context) {
 
    var isShowing = false
    private val TAG = FloatWindowManager::class.java.simpleName
    private var mContext: Context = context
    private var mFloatLayout = FloatLayout(mContext)
    private var mLayoutParams: WindowManager.LayoutParams? = null
    private var mWindowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
 
    fun createWindow() {
        TLogUtils.i(TAG, "createWindow: start...")
        // 对象配置操作使用apply,额外的处理使用also
        mLayoutParams = WindowManager.LayoutParams().apply {
            type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                // Android 8.0以后需要使用TYPE_APPLICATION_OVERLAY,不允许使用以下窗口类型来在其他应用和窗口上方显示提醒窗口:TYPE_PHONE、TYPE_PRIORITY_PHONE、TYPE_SYSTEM_ALERT、TYPE_SYSTEM_OVERLAY、TYPE_SYSTEM_ERROR。
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                // 在Android 8.0之前,悬浮窗口设置可以为TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口。
                // 在API Level  = 23的时候,需要在Android Manifest.xml文件中声明权限SYSTEM_ALERT_WINDOW才能在其他应用上绘制控件
                WindowManager.LayoutParams.TYPE_PHONE
            }
            // 设置图片格式,效果为背景透明
            format = PixelFormat.RGBA_8888
            // 设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)
            flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            // 调整悬浮窗显示的停靠位置为右侧置顶
            gravity = Gravity.TOP or Gravity.END
            width = 800
            height = 200
            x = 20
            y = 40
        }
 
        mWindowManager.addView(mFloatLayout, mLayoutParams)
        TLogUtils.i(TAG, "createWindow: end...")
        isShowing = true
    }
 
    fun showWindow() {
        TLogUtils.i(TAG, "showWindow: isShowing = $isShowing")
        if (!isShowing) {
            if (mLayoutParams == null) {
                createWindow()
            } else {
                mWindowManager.addView(mFloatLayout, mLayoutParams)
                isShowing = true
            }
        }
    }
 
    fun removeWindow() {
        TLogUtils.i(TAG, "removeWindow: isShowing = $isShowing")
        mWindowManager.removeView(mFloatLayout)
        isShowing = false
    }
 
}

FloatLayout 类及其 Layout

系统悬浮窗自定义View:FloatLayout

class FloatLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) :
    ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
 
    private var mTime: TCLTextView
    private var mDistance: TCLTextView
    private var mSpeed: TCLTextView
    private var mCalories: TCLTextView
 
    init {
        val view = LayoutInflater.from(context).inflate(R.layout.do_exercise_view_float_layout, this, true)
        mTime = view.findViewById(R.id.float_layout_tv_time)
        mDistance = view.findViewById(R.id.float_layout_tv_distance)
        mSpeed = view.findViewById(R.id.float_layout_tv_speed)
        mCalories = view.findViewById(R.id.float_layout_tv_calories)
    }
 
}

布局文件:float_layout_tv_time

HomeKeyObserverReceiver 类

class HomeKeyObserverReceiver : BroadcastReceiver() {
 
    override fun onReceive(context: Context?, intent: Intent?) {
        try {
            val action = intent!!.action
            val reason = intent.getStringExtra("reason")
            TLogUtils.d(TAG, "HomeKeyObserverReceiver: action = $action,reason = $reason")
            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS == action && "homekey" == reason) {
                val keyCode = intent.getIntExtra("keycode", KeyEvent.KEYCODE_UNKNOWN)
                TLogUtils.d(TAG, "keyCode = $keyCode")
                context?.stopService(Intent(context, FloatWindowService::class.java))
            }
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }
 
    companion object {
        private val TAG = HomeKeyObserverReceiver::class.java.simpleName
    }
 
}

FloatWindowUtils 类

object FloatWindowUtils {
 
    const val REQUEST_FLOAT_CODE = 1000
    private val TAG = FloatWindowUtils::class.java.simpleName
 
    /**
     * 判断Service是否开启
     */
    fun isServiceRunning(context: Context, ServiceName: String): Boolean {
        if (TextUtils.isEmpty(ServiceName)) {
            return false
        }
        val myManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val runningService = myManager.getRunningServices(1000) as ArrayList<ActivityManager.RunningServiceInfo>
        runningService.forEach {
            if (it.service.className == ServiceName) {
                return true
            }
        }
        return false
    }
 
    /**
     * 检查悬浮窗权限是否开启
     */
    @SuppressLint("NewApi")
    fun checkSuspendedWindowPermission(context: Activity, block: () -> Unit) {
        if (commonROMPermissionCheck(context)) {
            block()
        } else {
            Toast.makeText(context, "请开启悬浮窗权限", Toast.LENGTH_SHORT).show()
            context.startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
                data = Uri.parse("package:${context.packageName}")
            }, REQUEST_FLOAT_CODE)
        }
    }
 
    /**
     * 判断悬浮窗权限权限
     */
    fun commonROMPermissionCheck(context: Context?): Boolean {
        var result = true
        if (Build.VERSION.SDK_INT >= 23) {
            try {
                val clazz: Class<*> = Settings::class.java
                val canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context::class.java)
                result = canDrawOverlays.invoke(null, context) as Boolean
            } catch (e: Exception) {
                TLogUtils.e(TAG, e)
            }
        }
        return result
    }
 
}

总结

本文并未详细讨论系统悬浮窗的拖动功能,实现系统悬浮穿基本功能可以总结为以下几个步骤:

1. 声明及申请权限;
2. 构建悬浮窗需要的控件 Service、Receiver、Manager、Layout、Util;
3. 使用 WindowManager 创建、显示、销毁(以及更新)Layout。

到此这篇关于Kotlin实现Android系统悬浮窗详解的文章就介绍到这了,更多相关Android Kotlin系统悬浮窗内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Kotlin实现Android系统悬浮窗详解的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Kotlin难点解析:extension和this指针

    扩展是Kotlin语言中使用非常简单的一个特性。关于这个问题,其实我之前的一篇文章[[Kotlin]LambdaandExtension](https://www.jianshu.com/p/d7a...中有提到过。为了解决这个问题,官方提出了两个新的概念:dispatchreceiver和extensionreceiver。extensionreceiver:中文翻译为扩展接收者。为了简化,这里我们将dispatchreceiver简称为DR,将extensionreceiver简称为ER。如果你习惯了

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部