Android懸浮窗實現詳解

一、Android懸浮窗實現保活的缺點

在Android應用開發中,有時需要使用懸浮窗來實現一些特殊的需求,比如浮層彈窗、置頂通知等。但是懸浮窗會帶來一些問題,保活就是其中之一。當我們啟動懸浮窗時,系統需要給我們的應用分配系統資源,而此時如果經常操作該懸浮窗,那麼系統可能就會kill掉你的應用,導致懸浮窗無法正常顯示。當然,我們可以通過設置一些參數來緩解這個問題,比如設置懸浮窗類型、優化代碼等。

以下是一個簡單的示例代碼,用於設置懸浮窗類型為TYPE_SYSTEM_ALERT:

    WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
    layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
    layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;//設置懸浮窗類型
    layoutParams.format = PixelFormat.RGBA_8888;
    layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    //創建懸浮窗
    WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    View view = LayoutInflater.from(this).inflate(R.layout.layout_float_window, null);
    windowManager.addView(view, layoutParams);

二、Android開發懸浮窗

在Android中,懸浮窗的開發需要用到WindowManager類,同時需要注意懸浮窗的許可權問題,可以在Manifest.xml中添加如下許可權:

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

為了保證懸浮窗正常顯示,需要在代碼中動態添加許可權:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
    //開啟懸浮窗設置頁面
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
    context.startActivity(intent);
}

除了上述許可權的設置,我們還需要在懸浮窗布局中添加一個關閉按鈕,用於在關閉懸浮窗時釋放資源。以下代碼為一個示例:

//獲取關閉按鈕
Button btnCloseFloatWindow = (Button) viewFloatWindow.findViewById(R.id.btnCloseFloatWindow);
//添加關閉按鈕監聽器
btnCloseFloatWindow.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        windowManager.removeView(viewFloatWindow);//移除懸浮窗
    }
});

三、Android懸浮窗實現靈動島

靈動島是一個比較流行的懸浮窗應用,它的主要功能是在屏幕上顯示一個可供拖拽、伸縮和輪廓調整的懸浮窗,可以通過長按懸浮窗,彈出菜單執行不同的功能。以下是該應用的功能實現簡述:

首先,我們需要定義懸浮窗布局,並添加可拖動、伸縮和輪廓調整的屬性:

//獲取懸浮窗布局
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_floating, null);
//添加可拖動、伸縮和輪廓調整屬性
floatLayoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ?
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE,
        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
        PixelFormat.TRANSLUCENT);

接下來,我們需要監聽懸浮窗拖拽事件:

//獲取可拖動區域並添加觸摸事件監聽器
LinearLayout drag = floatingView.findViewById(R.id.dragView);
drag.setOnTouchListener(new View.OnTouchListener() {
    private int x;
    private int y;
    private float screenX;
    private float screenY;
    private float viewX;
    private float viewY;
    private int touchState;

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        boolean moved = false;
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN: //按下事件
                x = floatLayoutParams.x;
                y = floatLayoutParams.y;
                screenX = event.getRawX();
                screenY = event.getRawY();
                viewX = event.getX();
                viewY = event.getY();

                if (touchState == 0) {
                    touchState = 1;
                } else if (touchState == 2) {
                    touchState = 3;
                }
                break;
            case MotionEvent.ACTION_MOVE: //移動事件
                float moveX = event.getRawX() - screenX;
                float moveY = event.getRawY() - screenY;
                if (touchState == 1 && Math.abs(moveX) < 5 && Math.abs(moveY) < 5) {
                    break;
                }

                float moveViewX = event.getX() - viewX;
                float moveViewY = event.getY() - viewY;
                updateViewPosition((int) (x + moveX), (int) (y + moveY));
                updateViewSize((int) moveViewX, (int) moveViewY);
                moved = true;

                if (touchState == 1) {
                    touchState = 2;
                } else if (touchState == 3) {
                    touchState = 4;
                }
                break;
            case MotionEvent.ACTION_UP: //抬起事件
            case MotionEvent.ACTION_CANCEL: //取消事件
                if (!moved && touchState == 1) {
                    Intent intent = new Intent(FloatingService.this, LddWebViewActivity.class);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    startActivity(intent);
                }

                touchState = 0;
                break;
        }
        return true;
    }
});

最後,我們需要添加彈出菜單的功能:

//獲取菜單按鈕並添加點擊事件監聽器
ImageView menu = floatingView.findViewById(R.id.menuView);
menu.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        PopupMenu popupMenu = new PopupMenu(FloatingService.this, view);
        MenuInflater inflater = popupMenu.getMenuInflater();
        inflater.inflate(R.menu.menu_ldd, popupMenu.getMenu());

        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.sections:
                        Intent intent = new Intent(FloatingService.this, LddSectionsActivity.class);
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                        break;
                    case R.id.tools:
                        Intent intent2 = new Intent(FloatingService.this, LddToolsActivity.class);
                        intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent2);
                        break;
                    case R.id.exit:
                        stopSelf();
                        break;
                }
                return true;
            }
        });

        popupMenu.show();
    }
});

四、Android懸浮窗貼邊

懸浮窗貼邊可以提高應用使用的體驗效果,以下代碼實現了左、右、上、下四個方向的懸浮窗貼邊:

//獲取屏幕寬高
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;

//設置懸浮窗初始位置
floatLayoutParams.x = 0;
floatLayoutParams.y = 0;
windowManager.addView(floatingView, floatLayoutParams);

//獲取方向按鈕並添加點擊事件監聽器
ImageView leftView = floatingView.findViewById(R.id.leftView);
leftView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        floatLayoutParams.x = 0;
        windowManager.updateViewLayout(floatingView, floatLayoutParams);
    }
});
ImageView topView = floatingView.findViewById(R.id.topView);
topView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        floatLayoutParams.y = 0;
        windowManager.updateViewLayout(floatingView, floatLayoutParams);
    }
});
ImageView rightView = floatingView.findViewById(R.id.rightView);
rightView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        floatLayoutParams.x = screenWidth - floatingView.getWidth();
        windowManager.updateViewLayout(floatingView, floatLayoutParams);
    }
});
ImageView bottomView = floatingView.findViewById(R.id.bottomView);
bottomView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        floatLayoutParams.y = screenHeight - floatingView.getHeight();
        windowManager.updateViewLayout(floatingView, floatLayoutParams);
    }
});

五、Android Studio懸浮窗

Android Studio是當前使用最廣泛的Android應用開發工具,我們可以在它內部實現懸浮窗顯示。以下是一個示例代碼:

//獲取主布局
View content = findViewById(android.R.id.content);

//獲取windowManager
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);

//創建LayoutParams對象
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

//獲取懸浮窗布局
View floatView = LayoutInflater.from(MainActivity.this).inflate(R.layout.layout_float_window, null);

//添加懸浮窗插入到view中並設置位置
windowManager.addView(floatView, layoutParams);

六、Android懸浮窗源碼

以下是Android懸浮窗源碼:

public class FloatWindowService extends Service {
    private WindowManager.LayoutParams mParams;
    private WindowManager mWindowManager;
    private View mFloatView;

    @Override
    public void onCreate() {
        super.onCreate();

        //創建一個浮動窗口實例
        mFloatView = LayoutInflater.from(this).inflate(R.layout.float_layout, null);
        mParams = new WindowManager.LayoutParams();
        mParams.type = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE;
        mParams.format = PixelFormat.RGBA_8888;
        mParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        mParams.gravity = Gravity.LEFT | Gravity.TOP;
        mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

        //添加懸浮窗
        mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        assert mWindowManager != null;
        mWindowManager.addView(mFloatView, mParams);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mFloatView != null) {
            mWindowManager.removeView(mFloatView);
        }
    }
}

七、Android應用內懸浮窗

應用內懸浮窗適用於一些特殊的應用場景,例如掃描應用、音樂應用等。以下是一個示例代碼:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private SuspendButtonView suspendButtonView;
    private WindowManager windowManager;
    private WindowManager.LayoutParams layoutParams;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setUpView();
    }

    private void setUpView() {
        suspendButtonView = new SuspendButtonView(this);
        suspendButtonView.setOnClickListener(this);
        suspendButtonView.setImageResource(R.drawable.ic_launcher);
        layoutParams = new WindowManager.LayoutParams();
        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        layoutParams.format = PixelFormat.TRANSLUCENT;
        layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
        layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        windowManager.addView(suspendButtonView, layoutParams);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (suspendButtonView != null) {
            windowManager.removeView(suspendButtonView);
        }
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.suspend_button_view:
                break;
        }
    }
}

以上代碼定義了一個SuspendButtonView類,它繼承自ImageView,並實現了添加到懸浮窗的功能。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/302706.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-30 18:13
下一篇 2024-12-31 11:48

相關推薦

  • Android ViewPager和ScrollView滑動衝突問題

    Android開發中,ViewPager和ScrollView是兩個常用的控制項。但是當它們同時使用時,可能會發生滑動衝突的問題。本文將從多個方面介紹解決Android ViewPa…

    編程 2025-04-28
  • Android如何點擊其他區域收起軟鍵盤

    在Android應用中,當輸入框獲取焦點彈出軟鍵盤後,我們希望能夠點擊其他區域使軟鍵盤消失,以提升用戶體驗。本篇文章將說明如何實現這一功能。 一、獲取焦點並顯示軟鍵盤 在Andro…

    編程 2025-04-28
  • Android Studio HUD 實現指南

    本文將會以實例來詳細闡述如何在 Android Studio 中使用 HUD 功能實現菊花等待指示器的效果。 一、引入依賴庫 首先,我們需要在 build.gradle 文件中引入…

    編程 2025-04-27
  • Android和Vue3混合開發方案

    本文將介紹如何將Android和Vue3結合起來進行混合開發,以及其中的優勢和注意事項。 一、環境搭建 在進行混合開發之前,需要搭建好相應的開發環境。首先需要安裝 Android …

    編程 2025-04-27
  • Android Java Utils 可以如何提高你的開發效率

    Android Java Utils 是一款提供了一系列方便實用的工具類的 Java 庫,可以幫助開發者更加高效地進行 Android 開發,提高開發效率。本文將從以下幾個方面對 …

    編程 2025-04-27
  • 神經網路代碼詳解

    神經網路作為一種人工智慧技術,被廣泛應用於語音識別、圖像識別、自然語言處理等領域。而神經網路的模型編寫,離不開代碼。本文將從多個方面詳細闡述神經網路模型編寫的代碼技術。 一、神經網…

    編程 2025-04-25
  • Linux sync詳解

    一、sync概述 sync是Linux中一個非常重要的命令,它可以將文件系統緩存中的內容,強制寫入磁碟中。在執行sync之前,所有的文件系統更新將不會立即寫入磁碟,而是先緩存在內存…

    編程 2025-04-25
  • nginx與apache應用開發詳解

    一、概述 nginx和apache都是常見的web伺服器。nginx是一個高性能的反向代理web伺服器,將負載均衡和緩存集成在了一起,可以動靜分離。apache是一個可擴展的web…

    編程 2025-04-25
  • Linux修改文件名命令詳解

    在Linux系統中,修改文件名是一個很常見的操作。Linux提供了多種方式來修改文件名,這篇文章將介紹Linux修改文件名的詳細操作。 一、mv命令 mv命令是Linux下的常用命…

    編程 2025-04-25
  • git config user.name的詳解

    一、為什麼要使用git config user.name? git是一個非常流行的分散式版本控制系統,很多程序員都會用到它。在使用git commit提交代碼時,需要記錄commi…

    編程 2025-04-25

發表回復

登錄後才能評論