Android 懸浮窗功能實(shí)現(xiàn)(微信語音通話懸浮窗效果實(shí)現(xiàn))

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

WindowManager 官方文檔說明

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)圓直角切換效果

        val voiceFloatingView = VoiceFloatingView(context)
        voiceFloatingView.show()

5.最后

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載搏熄,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末暇赤,一起剝皮案震驚了整個(gè)濱河市心例,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鞋囊,老刑警劉巖止后,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異溜腐,居然都是意外死亡译株,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門挺益,熙熙樓的掌柜王于貴愁眉苦臉地迎上來歉糜,“玉大人,你說我怎么就攤上這事望众》瞬梗” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵烂翰,是天一觀的道長夯缺。 經(jīng)常有香客問我,道長甘耿,這世上最難降的妖魔是什么踊兜? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮佳恬,結(jié)果婚禮上捏境,老公的妹妹穿的比我還像新娘。我一直安慰自己殿怜,他們只是感情好典蝌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著头谜,像睡著了一般骏掀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天截驮,我揣著相機(jī)與錄音笑陈,去河邊找鬼。 笑死葵袭,一個(gè)胖子當(dāng)著我的面吹牛涵妥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坡锡,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼蓬网,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了鹉勒?” 一聲冷哼從身側(cè)響起帆锋,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎禽额,沒想到半個(gè)月后锯厢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脯倒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年实辑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藻丢。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剪撬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悠反,到底是詐尸還是另有隱情婿奔,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布问慎,位于F島的核電站,受9級(jí)特大地震影響挤茄,放射性物質(zhì)發(fā)生泄漏如叼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一穷劈、第九天 我趴在偏房一處隱蔽的房頂上張望笼恰。 院中可真熱鬧,春花似錦歇终、人聲如沸社证。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽追葡。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宜肉,已是汗流浹背匀钧。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谬返,地道東北人之斯。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像遣铝,于是被迫代替她去往敵國和親佑刷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容