溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android怎么實現懸浮窗

發布時間:2021-09-27 11:38:40 來源:億速云 閱讀:217 作者:小新 欄目:編程語言

這篇文章將為大家詳細講解有關Android怎么實現懸浮窗,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

1. 實現原理

1.1 懸浮窗插入接口

  在實現懸浮窗之前,我們需要知道通過什么接口,能夠將一個控件放入到屏幕中去。   Android的界面繪制,都是通過WindowMananger的服務來實現的。那么,既然要實現一個能夠在自身應用以外的界面上的懸浮窗,我們就要利用WindowManager來“做手腳”。

(frameworks/base/core/java/android/view/WindowMananger.java)

@SystemService(Context.WINDOW_SERVICE)public interface WindowManager extends ViewManager { ...}

  WindowManager實現了ViewManager接口,可以通過獲取WINDOW_SERVICE系統服務得到。而ViewManager接口有addView方法,我們就是通過這個方法將懸浮窗控件加入到屏幕中去。

1.2 權限設置及請求

  懸浮窗需要在別的應用之上顯示控件,很顯然,這需要某些權限才可以。

  在API Level >= 23的時候,需要在AndroidManefest.xml文件中聲明權限SYSTEM_ALERT_WINDOW才能在其他應用上繪制控件。

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

  除了這個權限外,我們還需要在系統設置里面對本應用進行設置懸浮窗權限。該權限在應用中需要啟動Settings.ACTION_MANAGE_OVERLAY_PERMISSION來讓用戶手動設置權限。

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

1.3 LayoutParam設置

  WindowManager的addView方法有兩個參數,一個是需要加入的控件對象,另一個參數是WindowManager.LayoutParam對象。

  這里需要著重說明的是LayoutParam里的type變量。這個變量是用來指定窗口類型的。在設置這個變量時,需要注意一個坑,那就是需要對不同版本的Android系統進行適配。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;} else { layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;}

  在Android 8.0之前,懸浮窗口設置可以為TYPE_PHONE,這種類型是用于提供用戶交互操作的非應用窗口。   而Android 8.0對系統和API行為做了修改,包括使用SYSTEM_ALERT_WINDOW權限的應用無法再使用一下窗口類型來在其他應用和窗口上方顯示提醒窗口:

- TYPE_PHONE - TYPE_PRIORITY_PHONE - TYPE_SYSTEM_ALERT - TYPE_SYSTEM_OVERLAY - TYPE_SYSTEM_ERROR

  如果需要實現在其他應用和窗口上方顯示提醒窗口,那么必須該為TYPE_APPLICATION_OVERLAY的新類型。   如果在Android 8.0以上版本仍然使用TYPE_PHONE類型的懸浮窗口,則會出現如下異常信息:

android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@f8ec928 -- permission denied for window type 2002

2. 具體實現

  下面來講解一下懸浮窗的具體實現方式。

  完整的源碼地址:https://github.com/dongzhong/TestForFloatingWindow

  為了讓懸浮窗與Activity脫離,使其在應用處于后臺時懸浮窗仍然可以正常運行,這里使用Service來啟動懸浮窗并做為其背后邏輯支撐。   在啟動服務之前,需要先判斷一下當前是否允許開啟懸浮窗。

(MainActivity.java)

public void startFloatingService(View view) { ... if (!Settings.canDrawOverlays(this)) { Toast.makeText(this, "當前無權限,請授權", Toast.LENGTH_SHORT); startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), 0); } else { startService(new Intent(MainActivity.this, FloatingService.class)); }}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 0) { if (!Settings.canDrawOverlays(this)) {  Toast.makeText(this, "授權失敗", Toast.LENGTH_SHORT).show(); } else {  Toast.makeText(this, "授權成功", Toast.LENGTH_SHORT).show();  startService(new Intent(MainActivity.this, FloatingService.class)); } }}

  懸浮窗控件可以是任意的View的子類類型。這里先以一個最簡單的Button來做示例。

(FloatingService.java)

@Overridepublic int onStartCommand(Intent intent, int flags, int startId) { showFloatingWindow(); return super.onStartCommand(intent, flags, startId);}private void showFloatingWindow() { if (Settings.canDrawOverlays(this)) { // 獲取WindowManager服務 WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); // 新建懸浮窗控件 Button button = new Button(getApplicationContext()); button.setText("Floating Window"); button.setBackgroundColor(Color.BLUE); // 設置LayoutParam WindowManager.LayoutParams 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.format = PixelFormat.RGBA_8888; layoutParams.width = 500; layoutParams.height = 100; layoutParams.x = 300; layoutParams.y = 300; // 將懸浮窗控件添加到WindowManager windowManager.addView(button, layoutParams); }}

  好了,完成了!

  對,沒看錯,最簡單的懸浮窗這就實現了。是不是很簡單?來看看效果吧。

  當然了,這個懸浮窗的效果僅僅是顯示出來,離真正想要的效果還相差甚遠。不過基礎的原理是已經實現了,剩下的就是要在這上面一點點的添加功能啦。

3. 增加小功能

3.1 拖動功能

  首先想要增加的功能就是能夠拖動這個懸浮窗。因為懸浮窗顯示的位置也許會擋住背后我們想要看到的信息,如果能夠把懸浮窗拖走那就最好了。   在Android中,觸摸事件的處理算是一個最基本操作了,直接上代碼。

(FloatingService.java)

private void showFloatingWindow() { ... button.setOnTouchListener(new FloatingOnTouchListener()); ...}private class FloatingOnTouchListener implements View.OnTouchListener { private int x; private int y; @Override public boolean onTouch(View view, MotionEvent event) { switch (event.getAction()) {  case MotionEvent.ACTION_DOWN:  x = (int) event.getRawX();  y = (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);  break;  default:  break; } return false; }}

  這里需要注意的是,在代碼注釋處的更新懸浮窗控件布局的方法。只有調用了這個方法,懸浮窗的位置才會發生改變??纯葱Ч?。

3.2 圖片自動播放

  下面我們對懸浮窗做一些小變動,來演示一下略微復雜一丟丟的界面。

  這里的懸浮窗界面我們不再單純的使用一個Button控件,而是在一個LinearLayout內加一個ImageView,布局文件如下。

(image_display.xml)

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ImageView android:id="@+id/image_display_imageview" android:layout_width="wrap_content" android:layout_height="wrap_content" /></LinearLayout>

  在創建懸浮窗布局的地方做一些修改。

(FloatingService.java)

private void showFloatingWindow() { ... LayoutInflater layoutInflater = LayoutInflater.from(this); displayView = layoutInflater.inflate(R.layout.image_display, null); displayView.setOnTouchListener(new FloatingOnTouchListener()); ImageView imageView = displayView.findViewById(R.id.image_display_imageview); imageView.setImageResource(images[imageIndex]); windowManager.addView(displayView, layoutParams); ...}

  我們還想讓圖片隔兩秒就切換一張,那么就再做一個定時切換圖片的機制吧。

(FloatingService.java)

@Overridepublic void onCreate() { ... changeImageHandler = new Handler(this.getMainLooper(), changeImageCallback);}private Handler.Callback changeImageCallback = new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == 0) {  imageIndex++;  if (imageIndex >= 5) {  imageIndex = 0;  }  if (displayView != null) {  ((ImageView) displayView.findViewById(R.id.image_display_imageview)).setImageResource(images[imageIndex]);  }  changeImageHandler.sendEmptyMessageDelayed(0, 2000); } return false; }};private void showFloatingWindow() { ... windowManager.addView(displayView, layoutParams); changeImageHandler.sendEmptyMessageDelayed(0, 2000);}

3.3 視頻小窗口

  下面我們就來看看懸浮窗最常用的功能:視頻小窗口。例如微信在視頻過程中退出界面,就會以小窗口的形式來顯示視頻。在這里,我先以MediaPlay和SurfaceView播放一個網絡視頻來模擬一下效果。實現起來與上面的圖片播放器基本相同,只是改變了控件和相應的播放邏輯。   布局文件類似上面的圖片播放器,只是把ImageView替換成了SurfaceView。   創建懸浮窗控件。

(FloatingService.java)

private void showFloatingWindow() { ... MediaPlayer mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); SurfaceView surfaceView = displayView.findViewById(R.id.video_display_surfaceview); final SurfaceHolder surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) {  mediaPlayer.setDisplay(surfaceHolder); } ... ); mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) {  mediaPlayer.start(); } }); try { mediaPlayer.setDataSource(this, Uri.parse("https://raw.githubusercontent.com/dongzhong/ImageAndVideoStore/master/Bruno%20Mars%20-%20Treasure.mp4")); mediaPlayer.prepareAsync(); } catch (IOException e) { Toast.makeText(this, "無法打開視頻源", Toast.LENGTH_LONG).show(); } windowManager.addView(displayView, layoutParams);}

關于“Android怎么實現懸浮窗”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女