最近做一个功能,由于我们设备上,没有功能键,所以需要实现一个功能,不管在设备上的哪个应用里,双指长按,就必须返回Launcher界面。
刚开始接这个需求,一脸懵逼,我去,这…之前都是在一个App里跳转来跳转去的,这可咋整,能咋整,不会写,那抄呗,网上一顿搜,搜到的资料很少,突然想到,全局手势返回桌面,那去安卓原生的全局手势代码那块瞅瞅呗
///WorkSpaces/LA.UM.9.15/LINUX/android/frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
//全局手势核心代码
@Override
public void onPointerEvent(MotionEvent event) {
if (mGestureDetector != null && event.isTouchEvent()) {
mGestureDetector.onTouchEvent(event);
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mSwipeFireable = true;
mDebugFireable = true;
mDownPointers = 0;
captureDown(event, 0);
if (mMouseHoveringAtEdge) {
mMouseHoveringAtEdge = false;
mCallbacks.onMouseLeaveFromEdge();
}
mCallbacks.onDown();
break;
case MotionEvent.ACTION_POINTER_DOWN:
captureDown(event, event.getActionIndex());
if (mDebugFireable) {
mDebugFireable = event.getPointerCount() < 5;
if (!mDebugFireable) {
if (DEBUG) Slog.d(TAG, "Firing debug");
mCallbacks.onDebug();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (mSwipeFireable) {
final int swipe = detectSwipe(event);
mSwipeFireable = swipe == SWIPE_NONE;
if (swipe == SWIPE_FROM_TOP) {
if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
mCallbacks.onSwipeFromTop();
} else if (swipe == SWIPE_FROM_BOTTOM) {
if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
mCallbacks.onSwipeFromBottom();
} else if (swipe == SWIPE_FROM_RIGHT) {
if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
mCallbacks.onSwipeFromRight();
} else if (swipe == SWIPE_FROM_LEFT) {
if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft");
mCallbacks.onSwipeFromLeft();
}
}
break;
case MotionEvent.ACTION_HOVER_MOVE:
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (!mMouseHoveringAtEdge && event.getY() == 0) {
mCallbacks.onMouseHoverAtTop();
mMouseHoveringAtEdge = true;
} else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) {
mCallbacks.onMouseHoverAtBottom();
mMouseHoveringAtEdge = true;
} else if (mMouseHoveringAtEdge
&& (event.getY() > 0 && event.getY() < screenHeight - 1)) {
mCallbacks.onMouseLeaveFromEdge();
mMouseHoveringAtEdge = false;
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mSwipeFireable = false;
mDebugFireable = false;
mCallbacks.onUpOrCancel();
break;
default:
if (DEBUG) Slog.d(TAG, "Ignoring " + event);
}
}
在这里,我们看到四个回调,就是我们常见的四个全局手势
interface Callbacks {
void onSwipeFromTop();
void onSwipeFromBottom();
void onSwipeFromRight();
void onSwipeFromLeft();
}
然后我们可以在这里看到他们的实现
//frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
DisplayPolicy(WindowManagerService service, DisplayContent displayContent) {
//.................
mSystemGestures = new SystemGesturesPointerEventListener(mContext, mHandler,
new SystemGesturesPointerEventListener.Callbacks() {
@Override
public void onSwipeFromTop() {
synchronized (mLock) {
if (mStatusBar != null) {
requestTransientBars(mStatusBar);
}
}
}
@Override
public void onSwipeFromBottom() {
synchronized (mLock) {
if (mNavigationBar != null
&& mNavigationBarPosition == NAV_BAR_BOTTOM) {
requestTransientBars(mNavigationBar);
}
}
}
@Override
public void onSwipeFromRight() {
final Region excludedRegion = Region.obtain();
synchronized (mLock) {
mDisplayContent.calculateSystemGestureExclusion(
excludedRegion, null /* outUnrestricted */);
final boolean sideAllowed = mNavigationBarAlwaysShowOnSideGesture
|| mNavigationBarPosition == NAV_BAR_RIGHT;
if (mNavigationBar != null && sideAllowed
&& !mSystemGestures.currentGestureStartedInRegion(
excludedRegion)) {
requestTransientBars(mNavigationBar);
}
}
excludedRegion.recycle();
}
@Override
public void onSwipeFromLeft() {
final Region excludedRegion = Region.obtain();
synchronized (mLock) {
mDisplayContent.calculateSystemGestureExclusion(
excludedRegion, null /* outUnrestricted */);
final boolean sideAllowed = mNavigationBarAlwaysShowOnSideGesture
|| mNavigationBarPosition == NAV_BAR_LEFT;
if (mNavigationBar != null && sideAllowed
&& !mSystemGestures.currentGestureStartedInRegion(
excludedRegion)) {
requestTransientBars(mNavigationBar);
}
}
excludedRegion.recycle();
}
@Override
public void onFling(int duration) {
if (mService.mPowerManagerInternal != null) {
mService.mPowerManagerInternal.powerHint(
PowerHint.INTERACTION, duration);
}
}
});
//...................
}
这里是对他们的具体实现的地方,所以如果我们的需求是上划返回桌面,那么只需在onSwipeFromBottom做返回操作就可以了。不过我们的需求是双指长按…
不对,我们刚才好像看到啥了,回过头去看看
///WorkSpaces/LA.UM.9.15/LINUX/android/frameworks/base/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@Override
public void onPointerEvent(MotionEvent event) {
if (mGestureDetector != null && event.isTouchEvent()) {
mGestureDetector.onTouchEvent(event);
}
//.......................
}
//继续往下看
public void systemReady() {
// GestureDetector records statistics about gesture classification events to inform gesture
// usage trends. SystemGesturesPointerEventListener creates a lot of noise in these
// statistics because it passes every touch event though a GestureDetector. By creating an
// anonymous subclass of GestureDetector, these statistics will be recorded with a unique
// source name that can be filtered.
// GestureDetector would get a ViewConfiguration instance by context, that may also
// create a new WindowManagerImpl for the new display, and lock WindowManagerGlobal
// temporarily in the constructor that would make a deadlock.
mHandler.post(() -> {
final int displayId = mContext.getDisplayId();
final DisplayInfo info = DisplayManagerGlobal.getInstance().getDisplayInfo(displayId);
if (info == null) {
// Display already removed, stop here.
Slog.w(TAG, "Cannot create GestureDetector, display removed:" + displayId);
return;
}
mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler) {
};
});
}
private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {
private OverScroller mOverscroller;
FlingGestureDetector() {
mOverscroller = new OverScroller(mContext);
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (!mOverscroller.isFinished()) {
mOverscroller.forceFinished(true);
}
return true;
}
@Override
public boolean onFling(MotionEvent down, MotionEvent up,
float velocityX, float velocityY) {
//............
return true;
}
}
///frameworks/base/core/java/android/view/GestureDetector.java
public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
OnContextClickListener {
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
public void onLongPress(MotionEvent e) {
}
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return false;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
public void onShowPress(MotionEvent e) {
}
public boolean onDown(MotionEvent e) {
return false;
}
public boolean onDoubleTap(MotionEvent e) {
return false;
}
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
public boolean onContextClick(MotionEvent e) {
return false;
}
}
可以看到底层已经实现好的一些方法比如单机,双击等供我们使用。
灵机一动,那我们在GestureDetector里实现双指长按的逻辑,然后在上面FlingGestureDetector里调用双指长按,在它里面去实现回到桌面的逻辑,不就OK了。
给SimpleOnGestureListener添加双指长按事件,然后在GestureDetector的onTouchEvent方法里,做双指长按事件触发逻辑,大概如下
private void handlePointerDown(MotionEvent ev, Float focusX) {
Log.d(TAG, "handlePointerDown");
mDoubleFingerPressStartMs = SystemClock.elapsedRealtime();
if (ev.getPointerCount() != 2 || !isDoubleFingerEvent()) {
return;
}
mDownFocusX = focusX;
if (mCurrentPointerDownEvent != null) {
mCurrentPointerDownEvent.recycle();
}
mCurrentPointerDownEvent = MotionEvent.obtain(ev);
mHandler.removeMessages(DOUBLE_FINGER_PRESS);
mHandler.sendEmptyMessageAtTime(DOUBLE_FINGER_PRESS, SystemClock.uptimeMillis() + 1000);
}
private boolean isDoubleFingerEvent() {
return mDoubleFingerPressStartMs - mSingleFingerPressStartMs <= 100;
}
private void handlePointerUp(MotionEvent ev) {
Log.d(TAG, "handlePointerUp");
if (ev.getPointerCount() != 2 || !isDoubleFingerEvent()) {
return;
}
mHandler.removeMessages(DOUBLE_FINGER_PRESS);
}
而在SystemGesturesPointerEventListener类的FlingGestureDetector这边实现添加的双指长按方法:
@Override
public boolean onDoubleFingerLongPress(MotionEvent e) {
if (DEBUG) Slog.d(TAG, "onDoubleFingerLongPress");
return true;
}
编译出包,刷进去之后,双指长按,发现日志顺利打出来,接下来,我们要考虑如何回到桌面
这个就容易多了,我们home键,或者back键都可以返回,但是back键的话,如果应用进入二级页面,那么就得执行两次双指长按操作,不符合需求。所以肯定考虑home事件。
参考
private void sendKey(int keyCode) {
try {
int inputSource = KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY | InputDevice.SOURCE_KEYBOARD;
long now = SystemClock.uptimeMillis();
boolean istrue = InputManager.getInstance().injectInputEvent(
new KeyEvent(
now, now, KeyEvent.ACTION_DOWN, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource
), InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
);
boolean isok = InputManager.getInstance().injectInputEvent(
new KeyEvent(
now, now, KeyEvent.ACTION_UP, keyCode, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, inputSource
), InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
);
} catch (Exception e) {
e.printStackTrace();
}
}
//所以上面我们只需要这样调用就可以返回桌面了。
@Override
public boolean onDoubleFingerLongPress(MotionEvent e) {
if (DEBUG) Slog.d(TAG, "onDoubleFingerLongPress");
sendKey(KeyEvent.KEYCODE_HOME);
return true;
}
总结: Framework代码,还是多看,看看优秀的人是咋写的
看完之后呢,照猫画虎哈,没事,慢慢就好了。