1.基本介紹
Android 界面繪制都是通過 WindowManager 服務(wù)來實(shí)現(xiàn)的占调,WindowManager 對(duì)象可通過獲取 WINDOW_SERVICE 系統(tǒng)服務(wù)得到柱彻,并因?yàn)?WindowManager 繼承于 ViewManager 许师,所以其擁有以下方法
addView(View view, ViewGroup.LayoutParams params)
主要通過該方法將指定 View 添加到屏幕上,實(shí)現(xiàn)懸浮窗效果
( WindowManager 對(duì)象調(diào)用 addView( ) 入?yún)⒌?LayoutParams 必須為 WindowManager.LayoutParams )
removeView(View view)
移除指定已添加到屏幕上的 View
updateViewLayout(View view, ViewGroup.LayoutParams params)
通過調(diào)整 LayoutParams 更新屏幕上的指定 View
2.代碼示例
2.1 在 AndroidManifest 中聲明權(quán)限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
2.2 判斷是否擁有在屏幕上層繪制權(quán)限
/**
* 判斷是否擁有懸浮窗權(quán)限
*
* @param isApplyAuthorization 是否申請(qǐng)權(quán)限
*/
public static boolean canDrawOverlays(Context context, boolean isApplyAuthorization) {
//Android 6.0 以下無需申請(qǐng)權(quán)限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//判斷是否擁有懸浮窗權(quán)限啊央,無則跳轉(zhuǎn)懸浮窗權(quán)限授權(quán)頁面
if (Settings.canDrawOverlays(context)) {
return true;
} else {
if (isApplyAuthorization) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + context.getPackageName()));
if (context instanceof Service) {
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
return false;
} else {
return false;
}
}
} else {
return true;
}
}
2.3 獲取 WindowManager 對(duì)象踢俄,生成 WindowManager.LayoutParams 聂宾,調(diào)用 addView 方法傳入指定 View
//獲取 WindowManager 服務(wù)
WindowManager windowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
//生成 WindowManager.LayoutParams 對(duì)象
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
//形成的窗口層級(jí)關(guān)系,Android 8.0 前后存在區(qū)別
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//實(shí)現(xiàn)在其他應(yīng)用和窗口上方顯示提醒窗口
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
//表示提供用戶交互操作的非應(yīng)用窗口
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams.format = PixelFormat.RGBA_8888;
//顯示位置
layoutParams.gravity = Gravity.START | Gravity.TOP;
//該flags描述的是窗口的模式虚倒,是否可以觸摸美侦,可以聚焦等
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
//窗口寬高
layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT;
layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
//需要懸浮的指定 View
View view = LayoutInflater.from(context).inflate(R.layout.widget_test_view, null, false);
//將指定 View 添加到屏幕上
windowManager.addView(view, layoutParams);
3.實(shí)現(xiàn)效果及便捷工具類
MainActivity 代碼(Kotlin,點(diǎn)擊查看源碼)
btn_show_view.setOnClickListener {
if (ExampleFloatingService.isStart) {
//通知處理點(diǎn)擊事件
LocalBroadcastManager.getInstance(this)
.sendBroadcast(Intent(ExampleFloatingService.ACTION_CLICK))
} else {
if (FloatingWindowHelper.canDrawOverlays(this, true)) {
startService(Intent(this, ExampleFloatingService::class.java))
}
}
}
ExampleFloatingService 代碼(Kotlin魂奥,點(diǎn)擊查看源碼)
懸浮窗管理服務(wù)菠剩,一般在此處理業(yè)務(wù)邏輯
目的脫離 Activity 展示懸浮窗
幫助類 FloatingWindowHelper(Java,點(diǎn)擊查看源碼)
懸浮窗功能相關(guān)操作幫助類耻煤,使用示例如下
private lateinit var mFloatingWindowHelper: FloatingWindowHelper
private lateinit var mExampleViewA: View
private lateinit var mExampleViewB: View
override fun onCreate() {
super.onCreate()
mFloatingWindowHelper = FloatingWindowHelper(this)
val layoutInflater = LayoutInflater.from(this)
mExampleViewA = layoutInflater.inflate(R.layout.widget_test_view, null, false)
mExampleViewB = layoutInflater.inflate(R.layout.widget_test_view_b, null, false)
}
private fun onClick() {
if (!mFloatingWindowHelper.contains(mExampleViewA)) {
//懸浮顯示指定 View
mFloatingWindowHelper.addView(mExampleViewA)
} else if (!mFloatingWindowHelper.contains(mExampleViewB)) {
//懸浮顯示指定 View 在指定位置具壮,并可拖動(dòng)
mFloatingWindowHelper.addView(mExampleViewB, 100, 100, true)
} else {
//移除所有懸浮 View
mFloatingWindowHelper.clear()
}
}
override fun onDestroy() {
mFloatingWindowHelper.destroy()
super.onDestroy()
}
4.仿微信語音通話懸浮窗效果實(shí)現(xiàn)
4.1 需求分析
需求分析:
1.懸浮可拖動(dòng)
2.自動(dòng)粘邊:停留時(shí)只粘在屏幕左邊或右邊
3.圓直角切換:拖動(dòng)時(shí)懸浮窗四個(gè)角為圓角,粘邊時(shí)粘邊處皆為直角
4.2 實(shí)現(xiàn)
- 1.懸浮可拖動(dòng)
通過 WindowManager addView 實(shí)現(xiàn)懸浮效果
通過重寫懸浮 View 的 onTouch 方法或?yàn)閼腋?View setOnTouchListener 處理觸摸事件記錄滑動(dòng)距離哈蝇,再通過 WindowManager updateViewLayout 方法更新懸浮 View 位置棺妓,實(shí)現(xiàn)懸浮可拖動(dòng)效果
- 2.自動(dòng)粘邊:停留時(shí)只粘在屏幕左邊或右邊
重寫懸浮 View 的 onTouch 方法感知觸摸事件,當(dāng)處于觸摸移動(dòng)狀態(tài)時(shí)炮赦,更新懸浮 View 四周角為圓角怜跑。當(dāng)觸摸抬起時(shí),根據(jù)懸浮 View 當(dāng)前坐標(biāo)與屏幕寬度值判斷位置吠勘,然后通過 WindowManager updateViewLayout 方法更新懸浮 View 位置性芬,將懸浮 View 平移到屏幕坐標(biāo)或右邊峡眶,實(shí)現(xiàn)粘邊效果
- 3.圓直角切換:拖動(dòng)時(shí)懸浮窗四個(gè)角為圓角,粘邊時(shí)粘邊處皆為直角
重寫懸浮 View 的 onDraw 方法植锉,根據(jù)移動(dòng)方向判斷是否需要在左/右邊繪制直角矩形覆蓋圓角辫樱,實(shí)現(xiàn)圓直角切換效果
-
4. 代碼示例
自定義懸浮 View (Java,點(diǎn)擊查看源碼)
調(diào)用(Kotlin)
val voiceFloatingView = VoiceFloatingView(context)
voiceFloatingView.show()
5.最后
- 源碼及 Demo 地址:https://github.com/ziwenL/FloatingWindowDemo
- 如有更好的見解或建議汽煮,歡迎留言