概述
各種測試,各種查資料,總算解決了底部上滑鎖屏功能的實作。一個底部三大金鋼折騰死個人。
最後借助的還是AccessibilityService實作。因為我想實作的底部導航欄一定要顯示在螢幕最底端,就算有三大金剛navigationbar,也要置于其頂層。
為了效果顯示,我沒有設定透明度,正常應用時,我是把背景色取消了

TYPE_ACCESSIBILITY_OVERLAY
TYPE_APPLICATION_OVERLAY
為什麼不用 TYPE_APPLICATION_OVERLAY 因為底部導航欄三大金鋼擋住無法接收觸發事件。
正常的懸浮窗使用這個是完美的,也是官方建議的,但對于有底部導航欄和狀态欄這種系統級的window在的時候,沒辦法,層級不夠。
不多說,直接上代碼解決方案
AccessibilityService
首先要繼承這個類,因為TYPE_ACCESSIBILITY_OVERLAY隻能在這個類裡面調用,否則就會報錯誤 Unable to add window – token null is not valid; is your activity running
public class MyAccessibilityService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
}
@Override
public void onInterrupt() {
}
}
為了實作懸浮窗的繪制,再重載兩個方法,開始想放在 oncreate 方法裡,出了點小問題,就放在onServiceConnected裡了
@Override
protected void onServiceConnected() {
initWindow();
super.onServiceConnected();
}
@Override
public void onDestroy() {
if (mView != null) windowManager.removeView(mView);
super.onDestroy();
}
具體的窗體加載方法,一些重要關鍵點在裡面加了注釋說明
@SuppressLint("ClickableViewAccessibility")
private void initWindow() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
//region 設定LayoutParams
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
layoutParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
} else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
}
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS ;
layoutParams.format = PixelFormat.RGBA_8888; //背景透明效果
int bottomheight = 20;
layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
layoutParams.height = bottomheight;
layoutParams.gravity = 51; //想要x,y生效,一定要指定Gravity為top和left //Gravity.TOP | Gravity.LEFT
layoutParams.x = 0; //啟動位置
Point point = new Point();
windowManager.getDefaultDisplay().getRealSize(point);
layoutParams.y = point.y - bottomheight;
//endregion
//加載懸浮窗布局
FloatPanelBottomBinding floatView = FloatPanelBottomBinding.inflate(LayoutInflater.from(MyAccessibilityService.this));
mView = floatView.getRoot();
//mView.setAlpha((float) 0.1);
floatView.tvPanel.setOnTouchListener(new View.OnTouchListener() {
//記錄初使按下時的坐标
float startY;
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_DOWN:
startY = motionEvent.getRawY();
break;
case MotionEvent.ACTION_UP:
float offsetY = motionEvent.getRawY() - startY;
if (offsetY < -6) {
//鎖屏
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
performGlobalAction(AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
}
break;
}
return true;
}
});
windowManager.addView(mView, layoutParams);
}
}
}
因為我要實作的上滑鎖屏,所用我隻處理了這一個功能,懸浮窗都出來,如果你想加更多功能還不是想要什麼有什麼?
AndroidManifest.xml
加服務,權重限,這些和正常的無障礙服務以及懸浮窗相同,本不打算提,怕有的朋友沒開發過類似應用,多加個說明吧
權重限
沒必要那麼多,隻要這一個就夠了
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
加服務
<!-- 全局傳回功能,鎖屏功能,開啟無障礙服務-->
<service android:name=".services.MyAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data android:name="android.accessibilityservice"
android:resource="@xml/accessibilityservice"/>
</service>
accessibilityservice.xml
res / xml / accessibilityservice.xml
配置檔案裡面也隻加個描述資訊就行,多了竟給手機加負擔
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_desc"/>
Activity
這個主要是檢查無障礙服務是否開啟,如果沒有開啟就跳轉過去,隻能手動開啟
//檢查是否開啟無障礙權限
public static boolean isAccessibilitySettingsOn(Context mContext, MyAccessibilityService accessibilityService) {
int accessibilityEnabled = 0;
final String service = mContext.getPackageName() + "/" + accessibilityService.getClass().getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException e) {
e.printStackTrace();
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibility = mStringColonSplitter.next();
if (accessibility.equalsIgnoreCase(service)) {
return true;
}
}
}
}
return false;
//測試時暫不做權限檢查
//return true;
}
//region權限檢查
if (!isAccessibilitySettingsOn(this, new MyAccessibilityService())) {
startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
}