Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android懸浮窗使用小結

Android懸浮窗使用小結

編輯:關於Android編程

Android的窗口體系中,WindowManager占有非常重要的地位,它封裝了添加、移除、更新窗口的方法,它是Activity、View的更加底層的管理類,使用WindowManager的其中一個例子就是制作懸浮窗或懸浮球之類的懸浮組件,這種懸浮組件不依賴某個Activity,它可以在任何界面顯示(只要你願意)。

這篇文章將對如何使用懸浮球做簡單總結,即使在android6.0下(android6.0使用動態權限管理),它也可以正常工作。

1.使用ButterKnife

ButterKnife可以方便的獲取到xml中定義的view的實例,比findViewById方便多了,使用ButterKnife非常簡單,可以總結為3步吧:

 

添加對應依賴

compile 'com.jakewharton:butterknife:7.0.0'

聲明id與對應的View


@Bind(R.id.start) Button start; @Bind(R.id.stop) Button stop; @Bind(R.id.bind) Button bind; @Bind(R.id.unbind) Button unbind;

獲取View

ButterKnife.bind(this);
在onCreate中調用它就可以。通過這句調用,start,stop,bind,unbind幾個Button都被實例化了。

2.使用Service

我希望懸浮窗是在Service中被顯示出來的,並且它的管理也在Service中實現。Service有兩種啟動方式,對應了不同的用途,使用bindService方式啟動的Service可以過得一個Ibinder的實例,使用這個實例可以操作Service的所有Public方法,一般用於Activity和Service交互比較頻繁的場合下,所以我們這裡使用startService啟動Service比較合理。 分析清楚以後,開始實現代碼:

2.1Service

public class FlowWindowService extends Service {
    private final String TAG = "FlowWindowService";
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG,"onCreate");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG,"onBind");
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG,"onStartCommand");
        showFlowWindow();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
    }

    public void showFlowWindow(){
        Log.v(TAG,"showFlowWindow");
        WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        Button button = new Button(getApplicationContext());
        button.setText("flow");
        button.setBackgroundColor(Color.RED);
        button.setWidth(100);
        button.setHeight(100);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.type = WindowManager.LayoutParams.TYPE_PHONE;
        params.format = PixelFormat.RGBA_8888;
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.width = 100;
        params.height = 100;
        params.x = 300;
        params.y = 300;
        windowManager.addView(button, params);
    }
}

這裡一次性把整個Service都貼出來了。這段代碼在Service的onStartCommand方法中創建了懸浮窗,創建懸浮窗非常簡單,使用WindowManager的addView添加一個View就好了,在Android6.0之前,是這樣的,Android6.0使用動態權限,會有點不同,這裡先使用21版本的sdk避免android6.0動態權限的問題,一切測試OK了再使用Android6.0的動態權限獲取響應權限。 懸浮窗的參數設置中,type是個比較重要的參數,它決定了你懸浮窗的優先級 服務不要忘記在Manifest中聲明:

2.2Activity中啟動Service

Activity非常簡單,它裡面只有四個Button:

MainActivity中使用ButterKnife實例化四個Button,並設置觸摸事件監聽器:
public class MainActivity extends AppCompatActivity {
    @Bind(R.id.start) Button start;
    @Bind(R.id.stop) Button stop;
    @Bind(R.id.bind) Button bind;
    @Bind(R.id.unbind) Button unbind;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                    startService(new Intent(MainActivity.this,FlowWindowService.class));
            }
        });
        stop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(new Intent(MainActivity.this,FlowWindowService.class));
        }
        });
        bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bindService(new Intent(MainActivity.this,FlowWindowService.class),connectionService, Context.BIND_AUTO_CREATE);
            }
        });
        unbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(connectionService);
            }
        });
    }
    ServiceConnection connectionService = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

bind和unbind按鈕知識我測試bindService和startService之間的差別用的,不用理會。最後聲明一個權限:

至此,界面如下: \
  點擊start,啟動服務: \ 懸浮窗出現。退出應用後它還在: \   退出應用後懸浮窗還在是因為我們沒有在Service退出的時候移除懸浮窗,在Service的onDestroy中移除即可:
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"onDestroy");
        windowManager.removeView(button);
    }
這意味著之前的代碼需要略作修改,button和windowManager都必須是類中定義的,而不是方法中定義的。
  這是Android6.0之前的步驟,Android6.0這麼弄就不可以了,原因是Android6.0使用動態權限策略。

2.3動態申請權限

不過也很簡單,代碼如下:
   private static final int REQUEST_PERMISSION_CODE = 1;
    private void requestCameraPermission() {
        requestPermissions(new String[]{Manifest.permission.SYSTEM_ALERT_WINDOW}, REQUEST_PERMISSION_CAMERA_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSION_CODE) {
            int grantResult = grantResults[0];
            boolean granted = grantResult == PackageManager.PERMISSION_GRANTED;
            Log.i(TAG, "onRequestPermissionsResult granted=" + granted);
        }
    }
使用requestPermisson方法申請SYSTEM_ALERT_WINDOW權限,REQUEST_PERMISSON_CODE是一個整數,用來表示這次請求,它的值隨意。requestPermissions會導致onRequestPermissionsResult方法被回調,在這個方法中我們就可以知道我們是不是申請到了權限。最後,在MainActivity的onCreate方法中申請權限即可。
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        requestPermission();
  如果權限申請失敗,很有可能是應用程序的權限太低,嘗試一下用系統簽名文件給它簽名,然後就OK了。另外,在Android的模擬器上是可以直接申請權限成功的。

2.4事件監聽

我們簡單給它添加單擊事件處理,每次點擊切換一下它的背景色:
    button.setOnClickListener(new View.OnClickListener() {
            int count = 0;
            @Override
            public void onClick(View v) {
               if((count++)%2==0){
                   button.setBackgroundColor(Color.GREEN);
               }else {
                   button.setBackgroundColor(Color.RED);
               }
            }
        });

3 移動懸浮框

移動懸浮框也很簡單,使用windowManager的updateViewLayout即可,下面的代碼制作了一個往復移動的懸浮框:
    Handler myHandler = new Handler();
    Runnable runnable = new Runnable() {
        boolean direct = true;
        @Override
        public void run() {
            Log.v(TAG,"params.x: "+params.x);
            if(direct){
                params.x+=10;
                if(params.x>800){
                    direct = false;
                }
            }else {
                params.x-=10;
                if(params.x<100){
                    direct = true;
                }
            }
            windowManager.updateViewLayout(button,params);
            if(!button.isAttachedToWindow()){
                Log.v(TAG,"not attach to window");
            }else{
                myHandler.postDelayed(this,50);
            }
        }
    };
    public void moveFlowButton(){
        Log.v(TAG,"moveFlowButton");
        myHandler.postDelayed(runnable,500);
    }

使用Handler提供定時器,效果如下: \  

4.解決not attached to window manager

這個程序運行的時候,點擊stop按鈕停止服務,會導致上面的問題,完整的log如下:
   Process: com.konka.flowwindowtest, PID: 19478
               java.lang.IllegalArgumentException: View=android.widget.Button{2553221 VFED..C.. ......I. 0,0-200,200} not attached to window manager
               at android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:456)
               at android.view.WindowManagerGlobal.updateViewLayout(WindowManagerGlobal.java:368)
               at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:99)
               at com.konka.flowwindowtest.FlowWindowService$1.run(FlowWindowService.java:89)
               at android.os.Handler.handleCallback(Handler.java:751)
               at android.os.Handler.dispatchMessage(Handler.java:95)
               at android.os.Looper.loop(Looper.java:154)
               at android.app.ActivityThread.main(ActivityThread.java:5969)
               at java.lang.reflect.Method.invoke(Native Method)
               at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:801)
               at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:691)
這個問題應該是Service已經銷毀了,但是button還想要更新位置造成的,所以應該在銷毀Service前先取消handler更新button的動畫。 首先定義一個線程退出標志:
Boolean destoryStatus = false;
在onDestory中是它為true即可:
        synchronized (this){
            destoryStatus = true;
        }
run方法如下:
    Runnable runnable = new Runnable() {
        boolean direct = true;
        @Override
        public void run() {
            if(!destoryStatus){
                Log.v(TAG,"params.x: "+params.x);
                if(direct){
                    params.x+=10;
                    if(params.x>800){
                        direct = false;
                    }
                }else {
                    params.x-=10;
                    if(params.x<100){
                        direct = true;
                    }
                }
                windowManager.updateViewLayout(button,params);
                myHandler.postDelayed(this,50);
            }
        }
    };
這樣就不會有這個問題了。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved