懸浮窗口的應用和應用場景和注意事項

懸浮窗口的實現主要是用windowManager來實現的,爲了簡單前面的基礎部分就copy過來:http://www.cnblogs.com/mythou/p/3244208.htmlhtml

一、WindowManager介紹android

  所有Android的窗口機制是基於一個叫作WindowManager實現,這個接口能夠添加view到屏幕,也能夠從屏幕刪除view。它面向的對象一端是屏幕,另外一端就是View,直接忽視咱們之前的Activity或者Dialog之類的元素。其實咱們的Activity或者Diolog底層的實現也是通過WindowManager,WindowManager是全局的,整個系統只有一個WindowManager。它是顯示View的最底層了。WindowManager主要用來管理窗口的一些狀態、屬性、view增長、刪除、更新、窗口順序、消息收集和處理等。經過Context.getSystemService(Context.WINDOW_SERVICE)的方式能夠得到WindowManager的實例.WindowManager繼承自ViewManager,裏面涉及到窗口管理的三個重要方法,分別是api

  • addView(); 
  • updateViewLayout();
  • removeView();

  在WindowManager中還有一個重要的靜態類LayoutParams。經過它能夠設置和得到當前窗口的一些屬性。咱們先來看看addView()方法,在addView中,會利用LayoutParams得到window的View屬性,併爲每一個window創ViewRoot,ViewRoot是View和WindowManager之間的橋樑,真正把View傳遞給WindowManager的是經過ViewRoot的setView()方法,ViewRoot實現了View和WindowManager之間的消息傳遞。在將主窗口添加到WindowManger時,它首先會創建一個代理對象:app

wm=(WindowManagerImpl)context.getSystemService(Context.WINDOW_SERVICE)ide

而且打開會話(IWindowSession),以後Window將經過該會話與WindowManager創建聯繫。佈局

二、關於android 6.0先後的受權問題。

可參考下:http://blog.csdn.net/self_study/article/details/50186435ui

本文主要是給一個服務添加懸浮窗口,首先判斷是否須要權限:this

一、6.0前清單文件添加:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />spa

二、6.0後除清單文件添加上面權限外還得手動爲應用添加權限:.net

  (1)在設置->應用->點開應用->配置應用->在其餘應用的上層顯示 授予權限

  (2)在代碼中判斷是否大於6.0版本,並判斷是否有權限,若沒有就跳到受權設置裏。

   (3)、將WindowManager 的類型設置爲: params.type = WindowManager.LayoutParams.TYPE_TOAST;則不須要申請權限

三、關於懸浮窗口的設置和實現以及一些屬性設置

  (1)、懸浮窗口在某一個activity裏顯示。

 以前作過一個VR相冊的項目,要求點擊圖片時候顯示一張圖片,爲了避免建立新的activity和佈局文件,只用一個自定義空間來顯示,採用了懸浮窗口,

    relativeLayout  =new  RelativeLayout(this);
    WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.width = WindowManager.LayoutParams.MATCH_PARENT;
        params.height = WindowManager.LayoutParams.MATCH_PARENT;
        params.format = PixelFormat.RGBA_8888;//背景透明
        params.gravity = Gravity.LEFT | Gravity.TOP;
        relativeLayout.addView(myBallPictureView);
        wm.addView(relativeLayout, params);    

然而發現了懸浮窗口的2個坑:

    <1> 、在addview()的時候報錯:android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application,根據報錯的緣由,是窗口的令牌錯誤,而後查看個人windowManager的初始化爲: 

          wm = (WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE);

    既然是令牌錯誤,就發現多是初始化wm時候發生了錯誤,後來經過activity和getApplicationContext()的contex是不同的,一個是整個應用的,一個是此activity的,後來換做activity來獲取WM,發現再也不報錯,且顯示很流暢。getApplicationContext()除非添加了

params.type = WindowManager.LayoutParams.TYPE_TOAST;後纔不報錯,但會一直顯示在屏幕上。。其實這裏提醒咱們能使用activity的context儘可能別使用getApplicationContext(),仍是有區別的(雖然大部分都時候獲取的token實際上是同樣的)。

    <2>、發現彈出了圖像後按back鍵圖像消失不了,爲何消失不了?經過log發現,當我按onBackPress()的時候發現,我覆寫的onBackPress()方法更本沒有執行,就聯想到多是WindowManager.LayoutParams 的Type屬性問題了。後來將Type修改成:

          params.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;

    發現能夠了。

這裏也使得咱們在使用懸浮窗口的時候主要的事項:WindowManager.LayoutParams的屬性,和context的獲取。下面介紹一下WindowManager.LayoutParams的Type和flag2個重要的屬性:

  Type是添加窗口的類型:窗口以系統的什麼類型的窗口顯示:可參考:http://realgodo.iteye.com/blog/1780176。用的較多的就是第二部分提到的TYPE_TOAST。此時會顯示在屏幕上,

  flag窗口的焦點屬性:   

      WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ;此時不會固定在屏幕上,按返回鍵是有效的。

      WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 此時窗口固定在屏幕上,返回鍵是無效的。

 (2) 懸浮窗口在全部屏幕上顯示

  這其實上面也提到了,須要設置TYPE_TOAST,或者TYPE_PRIORITY_PHONE    具體參看上面的關於Type的博客。

 (3),應用內顯示懸浮窗口

  這裏爲了方便管理,一般是設置一個service來實現,當應用前臺時候調用服務顯示窗口,當應用後臺時候調用服務顯示窗口。

(4)、桌面顯示,應用內不現實:這個和上面相反,也能夠用服務來實現。

  因爲時間緊,直接給出(3)代碼,其中有註解:

  Activity:

package com.example.user.floatingview;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements
        View.OnClickListener {
    private Button btn_show;
    private Button btn_hide;
    private  int floatShow = 0;
    private boolean hasGetPermission = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_show = (Button) findViewById(R.id.btn_show);
        btn_hide = (Button) findViewById(R.id.btn_hide);
        btn_show.setOnClickListener(this);
        btn_hide.setOnClickListener(this);
    }
    public void onClick(View v)
    {
        switch (v.getId())
        {
            case R.id.btn_show:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){//判斷api版本,若低於6.0就不須要動態受權檢測權限,將canDrawOverlays版本disable掉
                    hasGetPermission =Settings.canDrawOverlays(this);
                if (!hasGetPermission) {
                    Toast.makeText(this, "當前無權限,請受權!", Toast.LENGTH_SHORT).show();
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                            Uri.parse("package:" + getPackageName()));
                    startActivityForResult(intent, 1024);
                }
                    else {
                    floatShow = 1;
                    Toast.makeText(this, "後臺顯示浮動窗口", Toast.LENGTH_SHORT).show();
                }
            }
                break;
            case R.id.btn_hide:
                floatShow = -1;
                Toast.makeText(this,"已取消後臺顯示浮動窗口",Toast.LENGTH_SHORT).show();
                break;
        }
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        if(hasGetPermission) {
            if (floatShow == 1) {
                Intent show = new Intent(this, TopWindowService.class);
                show.putExtra(TopWindowService.OPERATION,
                        TopWindowService.OPERATION_SHOW);
                startService(show);
            } else if (floatShow == -1) {
                Intent hide = new Intent(this, TopWindowService.class);
                hide.putExtra(TopWindowService.OPERATION,
                        TopWindowService.OPERATION_HIDE);
                startService(hide);
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if(hasGetPermission&&floatShow == 1) {
            Intent hide = new Intent(this, TopWindowService.class);
            hide.putExtra(TopWindowService.OPERATION,
                    TopWindowService.OPERATION_HIDE);
            startService(hide);
        }
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 1024&&(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)){
            if(!Settings.canDrawOverlays(this)) {
                Toast.makeText(this, "權限授予失敗,沒法開啓懸浮窗", Toast.LENGTH_SHORT).show();
                hasGetPermission = false;
            } else {
                Toast.makeText(this, "權限授予成功!", Toast.LENGTH_SHORT).show();
                floatShow = 1;
            }

        }
    }



}
View Code

 service:

package com.example.user.floatingview;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.Button;

public class TopWindowService extends Service
{
    public static final String OPERATION = "operation";
    public static final int OPERATION_SHOW = 100;
    public static final int OPERATION_HIDE = 101;

    private static final int HANDLE_CHECK_ACTIVITY = 200;

    private boolean isAdded = false; // 是否已增長懸浮窗
    private static WindowManager wm;
    private static WindowManager.LayoutParams params;
    private Button btn_floatView;
    @Override
    public IBinder onBind(Intent intent)
    {
        return null;
    }

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

    @Override
    public void onDestroy()
    {
        super.onDestroy();
    }

    @Override
    public void onStart(Intent intent, int startId)
    {
        super.onStart(intent, startId);
        int operation = intent.getIntExtra(OPERATION, OPERATION_SHOW);
        switch (operation)
        {
            case OPERATION_SHOW:
                mHandler.sendEmptyMessage(OPERATION_SHOW);
                break;
            case OPERATION_HIDE:
                mHandler.sendEmptyMessage(OPERATION_HIDE);
                break;
        }
    }

    private Handler mHandler = new Handler()
    {
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case OPERATION_SHOW:
                    Log.v("zp","receive show message ");
                    if (!isAdded)
                    {
                        wm.addView(btn_floatView, params);
                        isAdded = true;
                    }
                    break;

                case OPERATION_HIDE:
                    Log.v("zp","receive hide message ");
                    if (isAdded)
                {
                    wm.removeView(btn_floatView);
                    isAdded = false;
                }
                break;
            }
        }
    };

    /**
     * 建立懸浮窗
     */
    private void createFloatView()
    {
        btn_floatView = new Button(getApplicationContext());
        btn_floatView.setText("hello");
        btn_floatView.setBackgroundResource(R.drawable.earth);
        btn_floatView.setAlpha(0.5f);//設置透明

        wm = (WindowManager) getApplicationContext().getSystemService(
                Context.WINDOW_SERVICE);
        params = new WindowManager.LayoutParams();

        // 設置window type
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
        // 若設置爲TYPE_TOAST 則不須要申請權限
        //params.type = WindowManager.LayoutParams.TYPE_TOAST;
        /*
         * 若是設置爲params.type = WindowManager.LayoutParams.TYPE_PHONE; 那麼優先級會下降一些,
         * 即拉下通知欄不可見
         */

        params.format = PixelFormat.RGBA_8888; // 設置圖片格式

        // 設置Window flag
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        /*
         * 下面的flags屬性的效果形同「鎖定」。 懸浮窗不可觸摸,不接受任何事件,同時不影響後面的事件響應。
         * wmParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL |
         * LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE;
         */

        // 設置懸浮窗的長得寬
        params.width = 150;
        params.height = 120;

        // 設置懸浮窗的Touch監聽
        btn_floatView.setOnTouchListener(new OnTouchListener()
        {
            int lastX, lastY;
            int paramX, paramY;

            public boolean onTouch(View v, MotionEvent event)
            {
                switch (event.getAction())
                {
                    case MotionEvent.ACTION_DOWN:
                        lastX = (int) event.getRawX();
                        lastY = (int) event.getRawY();
                        paramX = params.x;
                        paramY = params.y;
                        break;
                    case MotionEvent.ACTION_MOVE:
                        int dx = (int) event.getRawX() - lastX;
                        int dy = (int) event.getRawY() - lastY;
                        params.x = paramX + dx;
                        params.y = paramY + dy;
                        // 更新懸浮窗位置
                        wm.updateViewLayout(btn_floatView, params);
                        break;
                }
                return true;
            }
        });

        wm.addView(btn_floatView, params);
        isAdded = true;
    }

}
View Code

main_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btn_show"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="顯示桌面懸浮窗" />

    <Button
        android:id="@+id/btn_hide"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="隱藏桌面懸浮窗" />

</LinearLayout>
View Code
相關文章
相關標籤/搜索