Android 懸浮窗客峭,懸浮view功能實現(xiàn)

寫在前面:本文僅個人開發(fā)時遇到的問題及個人解決辦法的記錄。

國內(nèi)各個手機廠商對ROM魔改的比較嚴(yán)重抡柿,還沒有做兼容性測試舔琅,所以碰到沙雕的機子的時候,請再去尋找適配方法

最近項目開發(fā)中洲劣,需要實現(xiàn)一個懸浮窗备蚓,說一下實現(xiàn)方式,做一下記錄囱稽。

????????首先郊尝,簡單的藐視就是:實現(xiàn)懸浮窗是用的WindowManager。利用context.getSystemService(Context.WINDOW_SERVICE)獲取到WindowManger對象,調(diào)用里面的windowManager.addView(floatView, layoutParams)方法。floatView就是要展示的懸浮窗的View layoutParams是一些參數(shù)設(shè)置婉徘。

? ? ? ? 下面介紹一下實現(xiàn)步驟(懶得看的可以下滑到最下面看代碼):

? ? ? ? 申請權(quán)限弹灭,這是必須的一步习绢。

? ? ? ? ? ?在你的清單文件中添加如下權(quán)限代碼

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

然后在代碼里面使用下面的方法判斷是否有懸浮窗權(quán)限:

????????????Settings.canDrawOverlays(context)

如果沒有權(quán)限本姥,跳轉(zhuǎn)到權(quán)限開啟頁面稠曼,打開懸浮窗權(quán)限芥丧。確切的說是跳轉(zhuǎn)到開啟?允許顯示在其他應(yīng)用上層??的權(quán)限

startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 10086);

一切準(zhǔn)備工作完成后開始我們的正式任務(wù)暗笕蕖C朴!L哦АA肝搿!3跽D尽!I萑搿筝闹!

第一步,獲取到WindowManager對象腥光;

(WindowManager) context.getSystemService(Context.WINDOW_SERVICE)

第二步关顷,創(chuàng)建一個WindowManager.LayoutParams對象,用于設(shè)置一些懸浮view的參數(shù)

// 設(shè)置LayoutParam

layoutParams =new WindowManager.LayoutParams();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

}else {

layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;

}

//懸浮窗彈出的位置

layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;

//注意:這一個flags的設(shè)置武福,之前搜索很多實現(xiàn)都沒有設(shè)置這個议双,出現(xiàn)的情況就是在懸浮的view出現(xiàn)后? 點擊窗口其它地方?jīng)]有反應(yīng),是因為不設(shè)置這個參數(shù)捉片,懸浮窗彈出來后就占據(jù)整個窗口的焦點平痰。

layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

layoutParams.format = PixelFormat.RGBA_8888;

layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

layoutParams.x =0;

layoutParams.y =0;

第三步 獲取到需要懸浮顯示的view對象

LayoutInflater layoutInflater = LayoutInflater.from(context);

View floatView = layoutInflater.inflate(R.layout.floating_view, null);

第四步,將懸浮view和layoutParams調(diào)用windowmanager的方法addView顯示出來

// 將懸浮窗控件添加到WindowManager

windowManager.addView(floatView, layoutParams);

如果你需要對懸浮窗里不同view設(shè)置一些點擊事件

我們在上面第三步里面獲取到了懸浮窗的View對象伍纫,可以使用view.findviewById方法根據(jù)id拿到各個view宗雇,針對不同的view設(shè)置不同的事件。

對懸浮窗添加拖動事件

同樣上面第三步我們獲取到的view對象莹规,設(shè)置觸摸事件

//設(shè)置觸摸事件

floatView.setOnTouchListener(new FloatingOnTouchListener());

//因為我的懸浮窗需求比較簡單赔蒲,所以沒有那么多復(fù)雜的操作。只是拖動后良漱,讓懸浮view靠邊停著舞虱。

private class FloatingOnTouchListenerimplements View.OnTouchListener {

private int x;

? ? private int y;

? ? //標(biāo)記是否執(zhí)行move事件 如果執(zhí)行了move事件? 在up事件的時候判斷懸浮窗的位置讓懸浮窗處于屏幕左邊或者右邊

? ? private boolean isScroll;

? ? //標(biāo)記懸浮窗口是否移動了? 防止設(shè)置點擊事件的時候 窗口移動松手后觸發(fā)點擊事件

? ? private boolean isMoved;

? ? //事件開始時和結(jié)束的時候? X和Y坐標(biāo)位置

? ? private int startX;

? ? private int startY;

? ? @Override

? ? public boolean onTouch(View view, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

x = (int) event.getRawX();

? ? ? ? ? ? ? ? y = (int) event.getRawY();

? ? ? ? ? ? ? ? isMoved =false;

? ? ? ? ? ? ? ? isScroll =false;

? ? ? ? ? ? ? ? startX = (int) event.getRawX();

? ? ? ? ? ? ? ? startY = (int) event.getRawY();

break;

? ? ? ? ? ? case MotionEvent.ACTION_MOVE:

int nowX = (int) event.getRawX();

? ? ? ? ? ? ? ? int nowY = (int) event.getRawY();

? ? ? ? ? ? ? ? int movedX = nowX -x;

? ? ? ? ? ? ? ? int movedY = nowY -y;

? ? ? ? ? ? ? ? x = nowX;

? ? ? ? ? ? ? ? y = nowY;

? ? ? ? ? ? ? ? layoutParams.x =layoutParams.x + movedX;

? ? ? ? ? ? ? ? layoutParams.y =layoutParams.y + movedY;

? ? ? ? ? ? ? ? // 更新懸浮窗控件布局

? ? ? ? ? ? ? ? windowManager.updateViewLayout(view, layoutParams);

? ? ? ? ? ? ? ? isScroll =true;

break;

? ? ? ? ? ? case MotionEvent.ACTION_UP:

int stopX = (int) event.getRawX();

? ? ? ? ? ? ? ? int stopY = (int) event.getRawY();

? ? ? ? ? ? ? ? if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {

isMoved =true;

? ? ? ? ? ? ? ? }

if (isScroll){

autoView(view);

? ? ? ? ? ? ? ? }

break;

? ? ? ? }

return isMoved;

? ? }

//懸浮窗view自動停靠在屏幕左邊或者右邊

? ? private void autoView(View view) {

// 得到view在屏幕中的位置

? ? ? ? int[] location =new int[2];

? ? ? ? view.getLocationOnScreen(location);

? ? ? ? if (location[0]

layoutParams.x =0;

? ? ? ? }else {

layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();

? ? ? ? }

windowManager.updateViewLayout(view, layoutParams);

? ? }

}

最后债热,放出來一個簡單處理的類砾嫉,有需求的可以根據(jù)需求自己修改

代碼:


public class FloatingWindowUtils {

private Contextcontext;

? ? private int screenWidth;

? ? private WindowManager.LayoutParamslayoutParams;

? ? private WindowManagerwindowManager;

? ? private ViewfloatView;

? ? private FloatingWindowUtils() {

}

private static class InstanceHolder {

@SuppressLint("StaticFieldLeak")

private static final FloatingWindowUtilssInstance =new FloatingWindowUtils();

? ? ? ? private InstanceHolder() {

}

}

public static FloatingWindowUtilsgetInstance() {

return FloatingWindowUtils.InstanceHolder.sInstance;

? ? }

public void init(Context context) {

this.context = context;

? ? ? ? if (windowManager !=null)return;

? ? ? ? // 獲取WindowManager服務(wù)

? ? ? ? windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

? ? ? ? //獲取屏寬

? ? ? ? screenWidth = DensityUtils.getScreenSize(false).x;

? ? }

/**

? ? * 展示懸浮窗

? ? * @param layoutId 懸浮窗布局文件id

*/

? ? @SuppressLint("RtlHardcoded")

public void showFloatingWindow(@LayoutRes int layoutId){

// 新建懸浮窗控件

? ? ? ? LayoutInflater layoutInflater = LayoutInflater.from(context);

//? ? ? ? View floatView = layoutInflater.inflate(R.layout.floating_view, null);

? ? ? ? View floatView = layoutInflater.inflate(layoutId, null);

? ? ? ? if (floatView ==null){

throw new NullPointerException("懸浮窗view為null 檢查布局文件是否可用");

? ? ? ? }

showFloatingWindow(floatView);

? ? }

/**

? ? * 展示懸浮窗

? ? * @param floatView 懸浮窗view

*/

? ? @SuppressLint("RtlHardcoded")

public void showFloatingWindow(@NonNull View floatView){

if (this.floatView !=null)return;//有懸浮窗在顯示 不再顯示新的懸浮窗

? ? ? ? // 新建懸浮窗控件

? ? ? ? if (floatView ==null){

throw new NullPointerException("懸浮窗view為null 確認(rèn)view不為null");

? ? ? ? }

this.floatView = floatView;

? ? ? ? //設(shè)置觸摸事件

? ? ? ? floatView.setOnTouchListener(new FloatingOnTouchListener());

? ? ? ? //懸浮窗設(shè)置點擊事件

? ? ? ? floatView.setOnClickListener(new View.OnClickListener() {

@Override

? ? ? ? ? ? public void onClick(View v) {

Toast.makeText(context, "點擊了懸浮窗", Toast.LENGTH_SHORT).show();

? ? ? ? ? ? }

});

? ? ? ? // 設(shè)置LayoutParam

? ? ? ? layoutParams =new WindowManager.LayoutParams();

? ? ? ? if (Build.VERSION.SDK_INT? >= Build.VERSION_CODES.O) {

layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;

? ? ? ? }

layoutParams.gravity = Gravity.LEFT|Gravity.CENTER;

? ? ? ? //設(shè)置flags 不然懸浮窗出來后整個屏幕都無法獲取焦點,

? ? ? ? layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

? ? ? ? layoutParams.format = PixelFormat.RGBA_8888;

? ? ? ? layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;

? ? ? ? layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

? ? ? ? layoutParams.x =0;

? ? ? ? layoutParams.y =0;

? ? ? ? // 將懸浮窗控件添加到WindowManager

? ? ? ? windowManager.addView(floatView, layoutParams);

? ? }

/**

? ? * 隱藏懸浮窗

? ? */

? ? public void hideFloatWindow(){

if (floatView !=null){

windowManager.removeViewImmediate(floatView);

? ? ? ? ? ? floatView =null;

? ? ? ? }

}

public void unInit() {

hideFloatWindow();

? ? ? ? this.context =null;

? ? ? ? // 獲取WindowManager服務(wù)

? ? ? ? windowManager =null;

? ? }

private class FloatingOnTouchListenerimplements View.OnTouchListener {

private int x;

? ? ? ? private int y;

? ? ? ? //標(biāo)記是否執(zhí)行move事件 如果執(zhí)行了move事件? 在up事件的時候判斷懸浮窗的位置讓懸浮窗處于屏幕左邊或者右邊

? ? ? ? private boolean isScroll;

? ? ? ? //標(biāo)記懸浮窗口是否移動了? 防止設(shè)置點擊事件的時候 窗口移動松手后觸發(fā)點擊事件

? ? ? ? private boolean isMoved;

? ? ? ? //事件開始時和結(jié)束的時候? X和Y坐標(biāo)位置

? ? ? ? private int startX;

? ? ? ? private int startY;

? ? ? ? @Override

? ? ? ? public boolean onTouch(View view, MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

x = (int) event.getRawX();

? ? ? ? ? ? ? ? ? ? y = (int) event.getRawY();

? ? ? ? ? ? ? ? ? ? isMoved =false;

? ? ? ? ? ? ? ? ? ? isScroll =false;

? ? ? ? ? ? ? ? ? ? startX = (int) event.getRawX();

? ? ? ? ? ? ? ? ? ? startY = (int) event.getRawY();

break;

? ? ? ? ? ? ? ? case MotionEvent.ACTION_MOVE:

int nowX = (int) event.getRawX();

? ? ? ? ? ? ? ? ? ? int nowY = (int) event.getRawY();

? ? ? ? ? ? ? ? ? ? int movedX = nowX -x;

? ? ? ? ? ? ? ? ? ? int movedY = nowY -y;

? ? ? ? ? ? ? ? ? ? x = nowX;

? ? ? ? ? ? ? ? ? ? y = nowY;

? ? ? ? ? ? ? ? ? ? layoutParams.x =layoutParams.x + movedX;

? ? ? ? ? ? ? ? ? ? layoutParams.y =layoutParams.y + movedY;

? ? ? ? ? ? ? ? ? ? // 更新懸浮窗控件布局

? ? ? ? ? ? ? ? ? ? windowManager.updateViewLayout(view, layoutParams);

? ? ? ? ? ? ? ? ? ? isScroll =true;

break;

? ? ? ? ? ? ? ? case MotionEvent.ACTION_UP:

int stopX = (int) event.getRawX();

? ? ? ? ? ? ? ? ? ? int stopY = (int) event.getRawY();

? ? ? ? ? ? ? ? ? ? if (Math.abs(startX - stopX) >=1 || Math.abs(startY - stopY) >=1) {

isMoved =true;

? ? ? ? ? ? ? ? ? ? }

if (isScroll){

autoView(view);

? ? ? ? ? ? ? ? ? ? }

break;

? ? ? ? ? ? }

return isMoved;

? ? ? ? }

//懸浮窗view自動椭侠椋靠在屏幕左邊或者右邊

? ? ? ? private void autoView(View view) {

// 得到view在屏幕中的位置

? ? ? ? ? ? int[] location =new int[2];

? ? ? ? ? ? view.getLocationOnScreen(location);

? ? ? ? ? ? if (location[0]

layoutParams.x =0;

? ? ? ? ? ? }else {

layoutParams.x = DensityUtils.getScreenSize(false).x - view.getWidth();

? ? ? ? ? ? }

windowManager.updateViewLayout(view, layoutParams);

? ? ? ? }

}

public ViewgetFloatView(){

return floatView;

? ? }

}



最后給大家放一個介紹比較全面的帖子焕刮。

http://www.reibang.com/p/3246f7289704

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舶沿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子配并,更是在濱河造成了極大的恐慌括荡,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溉旋,死亡現(xiàn)場離奇詭異畸冲,居然都是意外死亡,警方通過查閱死者的電腦和手機观腊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門邑闲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梧油,你說我怎么就攤上這事苫耸。” “怎么了儡陨?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵褪子,是天一觀的道長。 經(jīng)常有香客問我骗村,道長嫌褪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任胚股,我火速辦了婚禮笼痛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘琅拌。我一直安慰自己晃痴,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布财忽。 她就那樣靜靜地躺著,像睡著了一般泣侮。 火紅的嫁衣襯著肌膚如雪即彪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天活尊,我揣著相機與錄音隶校,去河邊找鬼。 笑死蛹锰,一個胖子當(dāng)著我的面吹牛深胳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铜犬,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼舞终,長吁一口氣:“原來是場噩夢啊……” “哼轻庆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起敛劝,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤余爆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后夸盟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛾方,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年上陕,在試婚紗的時候發(fā)現(xiàn)自己被綠了桩砰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡释簿,死狀恐怖亚隅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辕万,我是刑警寧澤枢步,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站渐尿,受9級特大地震影響醉途,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜砖茸,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一隘擎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凉夯,春花似錦货葬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至征绎,卻和暖如春蹲姐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背人柿。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工柴墩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人凫岖。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓江咳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哥放。 傳聞我的和親對象是個殘疾皇子歼指,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

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

  • 在一些場合里爹土,我們使用懸浮窗會有很大的便利,比如IOS系統(tǒng)的懸浮窗东臀,360或者其他手機衛(wèi)士的懸浮窗等等着饥。 我們創(chuàng)造...
    杰奎琳子閱讀 1,246評論 0 12
  • 眾所周知,像qq音樂惰赋,當(dāng)退出qq音樂app時宰掉,歌詞會在桌面上顯示,這里可以用懸浮窗實現(xiàn)赁濒。 用一個很簡單的TextV...
    小瓜子兒姑娘閱讀 2,270評論 1 1
  • Android仿微信文章懸浮窗效果 序言 (轉(zhuǎn)載) 閱讀公眾號文章如果有人給你發(fā)微信你可以把這篇文章當(dāng)作懸浮窗懸浮...
    ListenToCode閱讀 1,454評論 1 18
  • 在調(diào)試時間敏感的程序時轨奄,實時在屏幕上打印當(dāng)前系統(tǒng)時間非常有用,下面就是利用Andorid懸浮窗口制作的一個小工具拒炎,...
    bely熊閱讀 887評論 0 0
  • 帶有android懸浮窗的語音識別語義理解demo 轉(zhuǎn)載請注明CSDN博文地址:http://blog.csdn....
    ls0609閱讀 2,296評論 0 7