一、前言
方案是参考的这位大佬的,大家可以去付费订阅支持一波。我大概理一下Android15的修改。
大佬的方案代码
二、Android15适配调整
1.bp调整,加入aidl引入,这样make之后就可以索引代码了
c
filegroup {
name: "launcher-src",
srcs: [
"src/**/*.java",
"src/**/*.kt",
"src/**/*.aidl"
],
}
客户端端按照文章来就行,服务端我微调了一下代码,让切换变得更加平顺,具体还的项目中再调整。其实还差手机重复按的时候停止动画之类的东西处理,遇到的时候再说吧。
java
package com.google.test;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.android.libraries.launcherclient.ILauncherOverlay;
import com.google.android.libraries.launcherclient.ILauncherOverlayCallback;
import androidx.annotation.NonNull;
/**
* Created by cczheng on 2022/5/25.
*/
public class ScreenService extends Service {
private Context mContext;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mLayoutParams;
private int screenWidth;
ILauncherOverlayCallback overlayCallback;
private String TAG = "ScreenService";
private float progress;
@Override
public void onCreate() {
super.onCreate();
mContext = this;
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
mLayoutParams = new WindowManager.LayoutParams();
DisplayMetrics outMetrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(outMetrics);
screenWidth = outMetrics.widthPixels;
Log.d(TAG, "onCreate");
}
@Override
public IBinder onBind(Intent intent) {
IBinder iBinder = new ILauncherOverlayImpl().asBinder();
Log.i(TAG, "onBind");
return iBinder;
}
public class ILauncherOverlayImpl extends ILauncherOverlay.Stub {
private Handler mainThreadHandler;
@Override
public String getVoiceSearchLanguage() throws RemoteException {
return null;
}
@Override
public boolean isVoiceDetectionRunning() throws RemoteException {
return false;
}
@Override
public void onPause() throws RemoteException {
Log.d(TAG, "onPause");
}
@Override
public void onResume() throws RemoteException {
Log.d(TAG, "onResume");
}
@Override
public void requestVoiceDetection(boolean start) throws RemoteException {
Log.d(TAG, "requestVoiceDetection");
}
@Override
public void openOverlay(int options) throws RemoteException {
Log.i(TAG, "openOverlay");
}
@Override
public void closeOverlay(int options) throws RemoteException {
Log.i(TAG, "closeOverlay");
}
@Override
public void startScroll() throws RemoteException {
Log.e(TAG, "startScroll");
Message.obtain(this.mainThreadHandler, OverlayCallback.START_SCROLL).sendToTarget();
}
@Override
public void onScroll(float progress) throws RemoteException {
// Log.i(TAG,"onScroll=" + progress);
Message.obtain(this.mainThreadHandler, OverlayCallback.UPDATE_SCROLL, progress).sendToTarget();
}
@Override
public void endScroll() throws RemoteException {
Log.e(TAG, "endScroll");
Message.obtain(this.mainThreadHandler, OverlayCallback.END_SCROLL).sendToTarget();
}
@Override
public void windowAttached(WindowManager.LayoutParams attrs, ILauncherOverlayCallback callbacks,
int options) throws RemoteException {
Log.i(TAG, "windowAttached.....");
// doWindowAttached(attrs, callbacks, options);
overlayCallback = callbacks;
Bundle bundle = new Bundle();
bundle.putParcelable("layout_params", attrs);
bundle.putInt("client_options", options);
OverlayCallback overlayCallback = new OverlayCallback(ScreenService.this);
mainThreadHandler = new Handler(Looper.getMainLooper(), overlayCallback);
Message.obtain(this.mainThreadHandler, OverlayCallback.WINDOW_ATTACHED,
Pair.create(bundle, callbacks)).sendToTarget();
}
@Override
public void windowDetached(boolean isChangingConfigurations) throws RemoteException {
Log.d(TAG, "windowDetached");
Message.obtain(this.mainThreadHandler, OverlayCallback.WINDOW_DETACHCHED, isChangingConfigurations).sendToTarget();
}
}
private void applyScroll(View view) {
try {
overlayCallback.overlayScrollChanged(progress);
mLayoutParams.x = (int) (-screenWidth*(1-progress));
mWindowManager.updateViewLayout(view, mLayoutParams);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
private void startProgressAnimation(boolean open,View view) {
ValueAnimator valueAnimator=(open)? ValueAnimator.ofFloat(progress, 1f):ValueAnimator.ofFloat(progress, 0f);
valueAnimator.setDuration(500);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
try {
overlayCallback.overlayScrollChanged(0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
progress= (float) valueAnimator.getAnimatedValue();
applyScroll(view);
}
});
valueAnimator.start();
}
class OverlayCallback implements Handler.Callback {
public static final int WINDOW_ATTACHED = 100;
public static final int START_SCROLL = 101;
public static final int UPDATE_SCROLL = 102;
public static final int END_SCROLL = 103;
public static final int WINDOW_DETACHCHED = 104;
private ScreenService screenService;
private LinearLayout mOverlayDecorView;
public OverlayCallback(ScreenService screenService) {
this.screenService = screenService;
}
@Override
public boolean handleMessage(@NonNull Message msg) {
try {
if (msg.what == WINDOW_ATTACHED) {
// bundle.putParcelable("layout_params", attrs);
// bundle.putInt("client_options", options);
Pair<Bundle, ILauncherOverlayCallback> pair = (Pair<Bundle, ILauncherOverlayCallback>) msg.obj;
WindowManager.LayoutParams layoutParams = pair.first.getParcelable("layout_params");
// overlayCallback = pair.second;
doWindowAttached(layoutParams, pair.second, 1);
} else if (msg.what == START_SCROLL) {
} else if (msg.what == UPDATE_SCROLL) {
progress = (float) msg.obj;
Log.d(TAG, "progress=" + progress);
applyScroll(mOverlayDecorView);
} else if (msg.what == END_SCROLL) {
startProgressAnimation(progress>0.4f,mOverlayDecorView);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return false;
}
public void doWindowAttached(WindowManager.LayoutParams lp, ILauncherOverlayCallback cb,
int flags) throws RemoteException {
mLayoutParams = new WindowManager.LayoutParams();
mLayoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
mLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
mLayoutParams.gravity = Gravity.START;
// 负一屏的 Window 层级比 Launcher 的大就可以
mLayoutParams.type = lp.type + 1;
mLayoutParams.token = lp.token;
mLayoutParams.flags = WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS |
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS |
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
mLayoutParams.x = -screenWidth;
Log.d(TAG, "doWindowAttached." + lp.type + " " + lp.token + " " + (-screenWidth));
mLayoutParams.format = PixelFormat.TRANSLUCENT;
mOverlayDecorView = new LinearLayout(mContext);
mOverlayDecorView.setGravity(Gravity.CENTER);
mOverlayDecorView.setBackgroundColor(Color.RED);
TextView textView = new TextView(mContext);
textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
textView.setText("XXXXXXXX");
textView.setTextSize(25);
textView.setTextColor(Color.WHITE);
mOverlayDecorView.addView(textView);
mWindowManager.addView(mOverlayDecorView, mLayoutParams);
mOverlayDecorView.setOnTouchListener(new OverlayOnTouchListener());
if (cb != null) {
cb.overlayStatusChanged(1);
}
}
}
private boolean isDrag;
private class OverlayOnTouchListener implements View.OnTouchListener {
private int firstX;
private int lastX;
@Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isDrag = false;
firstX = (int) event.getRawX();
lastX = (int) event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
isDrag = true;
int nowX = (int) event.getRawX();
int movedX = nowX - lastX;
lastX = nowX;
// Log.d(TAG,"movedX=" + movedX);
if (movedX < 0) {//只能左滑,滑到launcher中
mLayoutParams.x = mLayoutParams.x + movedX;
progress=1-((float) -mLayoutParams.x )/((float) screenWidth);
applyScroll(view);
// mWindowManager.updateViewLayout(view, mLayoutParams);
// try {
// overlayCallback.overlayScrollChanged();
// } catch (RemoteException e) {
// throw new RuntimeException(e);
// }
}
break;
case MotionEvent.ACTION_UP:
int stopX = (int) event.getRawX();
int movedFX = stopX - firstX;
Log.d(TAG,"lastX=" + lastX + " isDrag=" + isDrag + " movedFX=" + movedFX);
if (isDrag && movedFX < 0) {
progress=1-((float) -mLayoutParams.x )/((float) screenWidth);
startProgressAnimation(false,view);
}
break;
}
return isDrag || view.onTouchEvent(event);
}
}
}
