在智能管家App項目的開發過程中有一個這樣的功能,在服務中寫一個窗口,之後這個窗口需要通過Back鍵關閉,但是由於窗口是在服務Service中的,顯然我們無法像在Activity中通過回調onBackPressed()方法來關閉窗口,因此在網上查閱了事件分發的相關資料和劉某人程序員的博客後,總算學習到了如何解決該問題。
http://blog.csdn.net/qq_26787115/article/details/52260393
首先我們完成這個在服務中彈出Window的服務類
package com.liuguilin.keyevevtsample;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.IBinder;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
public class WindowService extends Service implements View.OnClickListener {
//窗口管理器
private WindowManager wm;
//view
private View mView;
//佈局參數
private WindowManager.LayoutParams layoutParams;
//取消window
private Button btnCloseWindow;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
initWindow();
}
/**
* 初始化Window
*/
private void initWindow() {
//窗口管理器
wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
//佈局參數
layoutParams = new WindowManager.LayoutParams();
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.flags =
//WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 不能觸摸
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
//格式
layoutParams.format = PixelFormat.TRANSLUCENT;
//類型
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
mView = View.inflate(getApplicationContext(), R.layout.layout_window_item, null);
btnCloseWindow = (Button) mView.findViewById(R.id.btnCloseWindow);
btnCloseWindow.setOnClickListener(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//顯示window
wm.addView(mView, layoutParams);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
/**
* 點擊事件
*
* @param view
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btnCloseWindow:
//取消window
wm.removeView(mView);
break;
}
}
}
除了注意在使用Window時要添加權限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
之外,
若使用的Android系統版本在API23之後(也就是Android6.0),彈出Window還需要手動爲應用允許一個OVERLAY的權限(這與運行時權限無關),詳細可以查看我在StackOverflow上看到的回答:
爲了方便測試添加一個Button
<Button
android:id="@+id/openWindow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="打開Window"
android:textAllCaps="false"/>
同時爲其添加點擊事件
findViewById(R.id.openWindow).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startService(new Intent(MainActivity.this, WindowService.class));
}
});
完成之後,我們首先看一下我們遇到的問題的表現效果:按Back鍵無法關閉Window
因此我們要想實現按Back鍵關閉Window,我們需要利用Window的佈局來攔截Back鍵的點擊事件,之後通過接口將事件傳遞到服務中,首先我們找到Window的佈局
mView = View.inflate(getApplicationContext(), R.layout.layout_window_item, null);
之後我們需要寫一個新的線性佈局來代替這個不能攔截Back鍵點擊事件的佈局
實現代碼如下
public class SessionLinearLayout extends LinearLayout {
private DispatchKeyEventListener mDispatchKeyEventListener;
public SessionLinearLayout(Context context) {
super(context);
}
public SessionLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SessionLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mDispatchKeyEventListener != null) {
return mDispatchKeyEventListener.dispatchKeyEvent(event);
}
return super.dispatchKeyEvent(event);
}
public DispatchKeyEventListener getDispatchKeyEventListener() {
return mDispatchKeyEventListener;
}
public void setDispatchKeyEventListener(DispatchKeyEventListener mDispatchKeyEventListener) {
this.mDispatchKeyEventListener = mDispatchKeyEventListener;
}
//監聽接口
public static interface DispatchKeyEventListener {
boolean dispatchKeyEvent(KeyEvent event);
}
}
這段代碼的思路需要理清,首先這是一個繼承自LinearLayout的佈局類,我們可以把他看成和LinearLayout是一樣的,之後我們在這個類中創建了一個監聽分發事件並根據這個事件做出相應邏輯的接口——DispatchKeyEventListener,之後重寫dispatchKeyEvent(KeyEvent event)方法,這個方法在我們點擊一些按鍵時會進行回調(如Back鍵),在這個方法中我們利用之前創建的接口來完成對應事件發生時的邏輯,有沒有發現這個實現方式和Button的setOnClickListener非常相似?這裏的setDispatchKeyEventListener就等同於Button中的setOnClickListener。
完成上述步驟後,我們需要將之前提到的佈局文件的根佈局從LinearLayout改成現在的SessionLinearLayout
<?xml version="1.0" encoding="utf-8"?>
<com.liuguilin.keyevevtsample.SessionLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.3"
android:background="@color/colorAccent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/btnCloseWindow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="關閉窗口"/>
</com.liuguilin.keyevevtsample.SessionLinearLayout>
最後再在服務中修改佈局的屬性爲SessionLinearLayout,併爲佈局設置分發事件的監聽器,在匿名內部類中完成點擊Back事件後的邏輯即可
public class WindowService extends Service implements View.OnClickListener {
//窗口管理器
private WindowManager wm;
//view
private SessionLinearLayout mView;
//佈局參數
private WindowManager.LayoutParams layoutParams;
//取消window
private Button btnCloseWindow;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
initWindow();
}
/**
* 初始化Window
*/
private void initWindow() {
//窗口管理器
wm = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
//佈局參數
layoutParams = new WindowManager.LayoutParams();
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.flags =
//WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | 不能觸摸
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
;
//格式
layoutParams.format = PixelFormat.TRANSLUCENT;
//類型
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
mView = (SessionLinearLayout) View.inflate(getApplicationContext(), R.layout.layout_window_item, null);
btnCloseWindow = (Button) mView.findViewById(R.id.btnCloseWindow);
btnCloseWindow.setOnClickListener(this);
//監聽返回鍵
mView.setDispatchKeyEventListener(mDispatchKeyEventListener);
}
/**
* 返回鍵監聽
*/
private SessionLinearLayout.DispatchKeyEventListener mDispatchKeyEventListener = new SessionLinearLayout.DispatchKeyEventListener() {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (mView.getParent() != null) {
wm.removeView(mView);
}
return true;
}
return false;
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//顯示window
wm.addView(mView, layoutParams);
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
}
/**
* 點擊事件
*
* @param view
*/
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btnCloseWindow:
//取消window
wm.removeView(mView);
break;
}
}
}
現在按Back鍵就能成功關閉窗口了!