Kotlin實(shí)現(xiàn)Android系統(tǒng)懸浮窗詳解
Android 彈窗淺談
我們知道 Android 彈窗中,有一類(lèi)彈窗會(huì)在應(yīng)用之外也顯示,這是因?yàn)樗簧昝鞒闪?strong>系統(tǒng)彈窗,除此之外還有2類(lèi)彈窗分別是:子彈窗與應(yīng)用彈窗。
應(yīng)用彈窗:就是我們常規(guī)使用的 Dialog 之類(lèi)彈窗,依賴(lài)于應(yīng)用的 Activity;子彈窗:依賴(lài)于父窗口,比如 PopupWindow;系統(tǒng)彈窗:比如狀態(tài)欄、Toast等,本文所講的系統(tǒng)懸浮窗就是系統(tǒng)彈窗。
系統(tǒng)懸浮窗具體實(shí)現(xiàn)
權(quán)限申請(qǐng)
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
代碼設(shè)計(jì)
下面的包結(jié)構(gòu)截圖,簡(jiǎn)單展示了實(shí)現(xiàn)系統(tǒng)懸浮窗的代碼結(jié)構(gòu),更復(fù)雜的業(yè)務(wù)需要可在此基礎(chǔ)上進(jìn)行擴(kuò)展。
FloatWindowService:系統(tǒng)懸浮窗在此 Service 中彈出;
FloatWindowManager:系統(tǒng)懸浮窗管理類(lèi);
FloatLayout:系統(tǒng)懸浮窗布局;
HomeKeyObserverReceiver:
監(jiān)聽(tīng) Home 鍵;
FloatWindowUtils:系統(tǒng)懸浮窗工具類(lèi)。
具體實(shí)現(xiàn)
FloatWindowService 類(lèi)
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 類(lèi)
包括系統(tǒng)懸浮窗的創(chuàng)建、顯示、銷(xiāo)毀(以及更新)。
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 類(lèi)代碼
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...") // 對(duì)象配置操作使用apply,額外的處理使用also mLayoutParams = WindowManager.LayoutParams().apply { type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Android 8.0以后需要使用TYPE_APPLICATION_OVERLAY,不允許使用以下窗口類(lèi)型來(lái)在其他應(yīng)用和窗口上方顯示提醒窗口:TYPE_PHONE、TYPE_PRIORITY_PHONE、TYPE_SYSTEM_ALERT、TYPE_SYSTEM_OVERLAY、TYPE_SYSTEM_ERROR。 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY } else { // 在A(yíng)ndroid 8.0之前,懸浮窗口設(shè)置可以為T(mén)YPE_PHONE,這種類(lèi)型是用于提供用戶(hù)交互操作的非應(yīng)用窗口。 // 在A(yíng)PI Level = 23的時(shí)候,需要在A(yíng)ndroid Manifest.xml文件中聲明權(quán)限SYSTEM_ALERT_WINDOW才能在其他應(yīng)用上繪制控件 WindowManager.LayoutParams.TYPE_PHONE } // 設(shè)置圖片格式,效果為背景透明 format = PixelFormat.RGBA_8888 // 設(shè)置浮動(dòng)窗口不可聚焦(實(shí)現(xiàn)操作除浮動(dòng)窗口外的其他可見(jiàn)窗口的操作) flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE // 調(diào)整懸浮窗顯示的??课恢脼橛覀?cè)置頂 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 類(lèi)及其 Layout
系統(tǒng)懸浮窗自定義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 類(lèi)
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 類(lèi)
object FloatWindowUtils { const val REQUEST_FLOAT_CODE = 1000 private val TAG = FloatWindowUtils::class.java.simpleName /** * 判斷Service是否開(kāi)啟 */ 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 } /** * 檢查懸浮窗權(quán)限是否開(kāi)啟 */ @SuppressLint("NewApi") fun checkSuspendedWindowPermission(context: Activity, block: () -> Unit) { if (commonROMPermissionCheck(context)) { block() } else { Toast.makeText(context, "請(qǐng)開(kāi)啟懸浮窗權(quán)限", Toast.LENGTH_SHORT).show() context.startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply { data = Uri.parse("package:${context.packageName}") }, REQUEST_FLOAT_CODE) } } /** * 判斷懸浮窗權(quán)限權(quán)限 */ 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 } }
總結(jié)
本文并未詳細(xì)討論系統(tǒng)懸浮窗的拖動(dòng)功能,實(shí)現(xiàn)系統(tǒng)懸浮穿基本功能可以總結(jié)為以下幾個(gè)步驟:
1. 聲明及申請(qǐng)權(quán)限;
2. 構(gòu)建懸浮窗需要的控件 Service、Receiver、Manager、Layout、Util;
3. 使用 WindowManager 創(chuàng)建、顯示、銷(xiāo)毀(以及更新)Layout。
到此這篇關(guān)于Kotlin實(shí)現(xiàn)Android系統(tǒng)懸浮窗詳解的文章就介紹到這了,更多相關(guān)Android Kotlin系統(tǒng)懸浮窗內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android Walker登錄記住密碼頁(yè)面功能實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Android Walker登錄記住密碼頁(yè)面功能的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05OpenGL?Shader實(shí)現(xiàn)陰影遮罩效果
這篇文章主要介紹了如何利用OpenGL?Shader實(shí)現(xiàn)陰影遮罩效果,文中的示例代碼簡(jiǎn)潔易懂,對(duì)我們學(xué)習(xí)OpenGL有一定幫助,需要的可以參考一下2022-02-02Android仿硅谷商城實(shí)現(xiàn)購(gòu)物車(chē)實(shí)例代碼
這篇文章主要介紹了Android購(gòu)物車(chē)編輯實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,一起跟隨小編過(guò)來(lái)看看吧2018-05-05Android 解析JSON對(duì)象及實(shí)例說(shuō)明
本篇文章小編為大家介紹,Android 解析JSON對(duì)象及實(shí)例說(shuō)明。需要的朋友參考下2013-04-04Flutter實(shí)現(xiàn)固定header底部滑動(dòng)頁(yè)效果示例
這篇文章主要為大家介紹了Flutter實(shí)現(xiàn)固定header底部滑動(dòng)頁(yè)效果示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Android實(shí)現(xiàn)漸變啟動(dòng)頁(yè)和帶有指示器的引導(dǎo)頁(yè)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)漸變啟動(dòng)頁(yè)和帶有指示器的引導(dǎo)頁(yè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-09-09android檢測(cè)網(wǎng)絡(luò)連接狀態(tài)示例講解
網(wǎng)絡(luò)的時(shí)候,并不是每次都能連接到網(wǎng)絡(luò),因此在程序啟動(dòng)中需要對(duì)網(wǎng)絡(luò)的狀態(tài)進(jìn)行判斷,如果沒(méi)有網(wǎng)絡(luò)則提醒用戶(hù)進(jìn)行設(shè)置2014-02-02Android實(shí)現(xiàn)圖片設(shè)置圓角形式
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)圖片設(shè)置圓角形式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Android 中三種啟用線(xiàn)程的方法總結(jié)
下面小編就為大家?guī)?lái)一篇Android 中三種啟用線(xiàn)程的方法總結(jié)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-02-02