irpas技术客

Android 手势导航(Launcher3 部分)_虫师魁拔_android 手势导航

irpas 7920

手势导航功能的实现主要由 SystemUI + Launcher3 共同处理,SystemUI? 中主要由 OverviewProxyService.java 监听,而在 Launcher3 中启动一个 TouchInteractionService 服务监听,主要代码实现都由 Launcher 中处理。

Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java

private void initInputMonitor() { disposeEventHandlers(); if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) { return; } Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up", mDeviceState.getDisplayId()); mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR); // 注册处理 view input 事件,在 onInputEvent 中进行处理 mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(), mMainChoreographer, this::onInputEvent); mDeviceState.updateGestureTouchRegions(); } ... ... private void onInputEvent(InputEvent ev) { ... ... final int action = event.getAction(); if (action == ACTION_DOWN) { ... ... // 判断是手势底部向上滑动 if (mDeviceState.isInSwipeUpTouchRegion(event)) { ... ... GestureState prevGestureState = new GestureState(mGestureState); GestureState newGestureState = createGestureState(mGestureState); mConsumer.onConsumerAboutToBeSwitched(); mGestureState = newGestureState; // 根据当前实际情况创建不同的 InputConsumer mConsumer = newConsumer(prevGestureState, mGestureState, event); mUncheckedConsumer = mConsumer; ... ... } else { // 其他 MOVE UP CANCEL 事件处理 if (mUncheckedConsumer != InputConsumer.NO_OP) { // 处理滑动动画效果 mDeviceState.setOrientationTransformIfNeeded(event); } } boolean cleanUpConsumer = (action == ACTION_UP || action == ACTION_CANCEL) && mConsumer != null && !mConsumer.getActiveConsumerInHierarchy().isConsumerDetachedFromGesture(); // 交由具体的 InputConsumer 去继续处理 mUncheckedConsumer.onMotionEvent(event); // 结束 reset 状态 if (cleanUpConsumer) { reset(); } }

TouchInteractionService 是 Launcher 中开始地方

initInputMonitor() 函数中注册 onInputEvent 事件监听。这个 onInputEvent 从 BatchedInputEventReceiver(继承 InputEventReceiver.java) 的 onInputEvent 调用。

onInputEvent?函数中处理滑动事件,在 DOWN 事件时根据不同的场景创建不同的 InputConsumer,例如在桌面、或其他界面等不同情况下使用手势,对应的?InputConsumer 是不同的,最常见的就是?OtherActivityInputConsumer (其他Activity界面使用手势导航)。

Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java

public void onMotionEvent(MotionEvent ev) { switch (ev.getActionMasked()) { case ACTION_DOWN: { // 非关键代码 break; } case ACTION_MOVE: { int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == INVALID_POINTER_ID) { break; } mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); float displacement = getDisplacement(ev); float displacementX = mLastPos.x - mDownPos.x; float displacementY = mLastPos.y - mDownPos.y; if (!mPassedWindowMoveSlop) { if (!mIsDeferredDownTarget) { // Normal gesture, ensure we pass the drag slop before we start tracking // the gesture if (Math.abs(displacement) > mTouchSlop) { mPassedWindowMoveSlop = true; mStartDisplacement = Math.min(displacement, -mTouchSlop); } } } float horizontalDist = Math.abs(displacementX); float upDist = -displacement; boolean passedSlop = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop; if (!mPassedSlopOnThisGesture && passedSlop) { mPassedSlopOnThisGesture = true; } // Until passing slop, we don't know what direction we're going, so assume // we're quick switching to avoid translating recents away when continuing // the gesture (in which case mPassedPilferInputSlop starts as true). boolean haveNotPassedSlopOnContinuedGesture = !mPassedSlopOnThisGesture && mPassedPilferInputSlop; boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture || horizontalDist > upDist; if (!mPassedPilferInputSlop) { if (passedSlop) { if (mDisableHorizontalSwipe && Math.abs(displacementX) > Math.abs(displacementY)) { // Horizontal gesture is not allowed in this region forceCancelGesture(ev); break; } mPassedPilferInputSlop = true; if (mIsDeferredDownTarget) { // 启动动画 startTouchTrackingForWindowAnimation(ev.getEventTime()); } if (!mPassedWindowMoveSlop) { mPassedWindowMoveSlop = true; mStartDisplacement = Math.min(displacement, -mTouchSlop); } // 通知开始手势滑动 notifyGestureStarted(isLikelyToStartNewTask); } } if (mInteractionHandler != null) { if (mPassedWindowMoveSlop) { // 更新移动位置 mInteractionHandler.updateDisplacement(displacement - mStartDisplacement); } // 更新移动检测 if (mDeviceState.isFullyGesturalNavMode()) { mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement || isLikelyToStartNewTask); mMotionPauseDetector.addPosition(ev); mInteractionHandler.setIsLikelyToStartNewTask(isLikelyToStartNewTask); } } break; } case ACTION_CANCEL: case ACTION_UP: { if (DEBUG_FAILED_QUICKSWITCH && !mPassedWindowMoveSlop) { float displacementX = mLastPos.x - mDownPos.x; float displacementY = mLastPos.y - mDownPos.y; Log.d("Quickswitch", "mPassedWindowMoveSlop=false" + " disp=" + squaredHypot(displacementX, displacementY) + " slop=" + mSquaredTouchSlop); } finishTouchTracking(ev); break; } } }

OtherActivityInputConsumer 是具体处理的类。主要都在 onMotionEvent ACTION_MOVE 事件做处理。

startTouchTrackingForWindowAnimation?函数中进行?mInteractionHandler 等初始化操作及设置动画开始。

notifyGestureStarted?函数中设置开始手势滑动状态。

接下来的?if (mInteractionHandler != null) 代码块中就是具体滑动时候的动画缩放显示等操作。

finishTouchTracking(ev)?函数中通知滑动结束,通知最终状态。

private void finishTouchTracking(MotionEvent ev) { ... ... if (mPassedWindowMoveSlop && mInteractionHandler != null) { if (ev.getActionMasked() == ACTION_CANCEL) { // 手势滑动取消 mInteractionHandler.onGestureCancelled(); } else { // 手势滑动正常结束 mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.get(this).getScaledMaximumFlingVelocity()); float velocityX = mVelocityTracker.getXVelocity(mActivePointerId); float velocityY = mVelocityTracker.getYVelocity(mActivePointerId); float velocity = mNavBarPosition.isRightEdge() ? velocityX : mNavBarPosition.isLeftEdge() ? -velocityX : velocityY; // up 动作时最后修改一次位置 mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement); // 通知滑动结束 mInteractionHandler.onGestureEnded(velocity, new PointF(velocityX, velocityY), mDownPos); } } ... ... }

判断最终是执行的 HOMO 还是 RECENTS 等事件是在?mInteractionHandler (BaseSwipeUpHandlerV2.java) 中根据滑动中的数据具体判断。

Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java

public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) { float flingThreshold = mContext.getResources() .getDimension(R.dimen.quickstep_fling_threshold_velocity); boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold; mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); mLogAction = isFling ? Touch.FLING : Touch.SWIPE; boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x); if (isVelocityVertical) { mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN; } else { mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT; } mDownPos = downPos; handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */); } private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, boolean isCancel) { PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000); long duration = MAX_SWIPE_DURATION; float currentShift = mCurrentShift.value; // 根据滑动数值判断最终是什么类型事件 final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity, isFling, isCancel); float endShift = endTarget.isLauncher ? 1 : 0; final float startShift; Interpolator interpolator = DEACCEL; if (!isFling) { long expectedDuration = Math.abs(Math.round((endShift - currentShift) * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); startShift = currentShift; interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL; } else { startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); float minFlingVelocity = mContext.getResources() .getDimension(R.dimen.quickstep_fling_min_velocity); if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) { Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams( startShift, endShift, endShift, endVelocity / 1000, mTransitionDragLength, mContext); endShift = overshoot.end; interpolator = overshoot.interpolator; duration = Utilities.boundToRange(overshoot.duration, MIN_OVERSHOOT_DURATION, MAX_SWIPE_DURATION); } else { float distanceToTravel = (endShift - currentShift) * mTransitionDragLength; // we want the page's snap velocity to approximately match the velocity at // which the user flings, so we scale the duration by a value near to the // derivative of the scroll interpolator at zero, ie. 2. long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); if (endTarget == RECENTS) { interpolator = OVERSHOOT_1_2; } } } } if (endTarget.isLauncher && mRecentsAnimationController != null) { mRecentsAnimationController.enableInputProxy(mInputConsumer, this::createNewInputProxyHandler); } if (endTarget == HOME) { setShelfState(ShelfAnimState.CANCEL, LINEAR, 0); duration = Math.max(MIN_OVERSHOOT_DURATION, duration); } else if (endTarget == RECENTS) { LiveTileOverlay.INSTANCE.startIconAnimation(); if (mRecentsView != null) { int nearestPage = mRecentsView.getPageNearestToCenterOfScreen(); if (mRecentsView.getNextPage() != nearestPage) { // We shouldn't really scroll to the next page when swiping up to recents. // Only allow settling on the next page if it's nearest to the center. mRecentsView.snapToPage(nearestPage, Math.toIntExact(duration)); } if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) { mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION); } duration = Math.max(duration, mRecentsView.getScroller().getDuration()); } if (mDeviceState.isFullyGesturalNavMode()) { setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration); } } // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() // or resumeLastTask(). if (mRecentsView != null) { mRecentsView.setOnPageTransitionEndCallback( () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED)); } else { mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED); } animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs); }

最终以 handleNormalGestureEnd 结束,这里?calculateEndTarget 进行判断最终的手势滑动动作是哪种

系统设置有四种手势动作:

HOME 回到主界面

RECENTS 多任务界面

NEW_TASK 切换到新的应用

LAST_TASK 仍然停留在当前界面


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Android #手势导航 #手势导航功能的实现主要由 #SystemUI #Launcher3 #共同处理在