手势导航功能的实现主要由 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;// 根据当前实际情况创建不同的 InputConsumermConsumer = 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 gestureif (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 regionforceCancelGesture(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 仍然停留在当前界面

Android 手势导航(Launcher3 部分)相关推荐

  1. Android 手势导航(从下往上滑动进入多任务页面)

    Android系统启动篇 1,<android系统启动流程简介> 2,<android init进程启动流程> 3,<android zygote进程启动流程> 4 ...

  2. recyclerview 滚动冲突_如何处理手势冲突 | 手势导航连载 (三)

    作者 / Chris Banes, Android 开发者关系团队工程师 我们将在近期为大家带来一个关于 "手势导航" 的系列连载,本文是手势导航连载的第三篇,如果您希望查看前两篇 ...

  3. 开启全面屏体验 | 手势导航 (一)

    作者 / Chris Banes, Android 开发者关系团队工程师 本文是手势导航连载的第一篇文章,在接下来的时间里,我们将会为大家带来一系列手势导航的话题,敬请关注! 我们在 Android ...

  4. Android 10 手势导航源码分析

    Android O之前的虚拟按键,基本的控制方法都是在SystemUI中做处理的,在Android 10上为了在手势导航操作时其动画更加流畅,与Launcher互动效果更好,google的设计师就把手 ...

  5. android studio 顶部导航栏_Android10 手势导航开发与处理:边到边(I)

    这是我们有关"手势导航"系列的第一篇文章. 借助Android 10,已添加了新的系统导航模式,允许用户向后导航,导航至主屏幕并通过手势触发设备助手. Android 10 中新手 ...

  6. Android 11.0 自定义仿小米全面屏手势导航左右手势滑动返回UI效果

    目录 1.概述 2.自定义仿小米全面屏手势导航返回ui布局的核心代码 3.自定义左右手势返回UI样式的核心代码功能分析 3.1 NavigationBarView手势导航布局左右手势返回的相关代码 3 ...

  7. 如何在任何Android手机上获取手势导航

    Android's upcoming iteration (currently just called "P") contains a new gesture navigation ...

  8. android之适配华为手机手势导航方式

    高仿滴滴首页滑动布局 做这个的时候需要计算出首页底部布局距离顶部的高度,要把虚拟导航栏高度算在里面 但是发现了一个华为的奇葩bug,华为emui系统的系统导航里可以选手势导航和虚拟导航等方式 选用手势 ...

  9. android 按钮手势,如何在Android 10中使用手势导航或如何关闭它

    谷歌移动操作系统的最新版本Android 10带有许多出色的新功能.在Android 9.0 Pie中向Android中引入了手势导航 - 它使用滑动和其他操作来控制手机,而不是点击按钮.在Andro ...

  10. Android 12默认手势导航及bug修复

    Android 12默认手势导航方法有2种 第1种方法:配置如下ro即可 ro.boot.vendor.overlay.theme=com.android.internal.systemui.navb ...

最新文章

  1. Tensorflow |(5)模型保存与恢复、自定义命令行参数
  2. 新电脑装win7_微软正式跟Win7系统告别了!国产电脑操作新系统诞生:系统更加美观...
  3. 信息太多,时间太少: 大脑如何区分重要和不重要的事?
  4. [转]Docker基础-使用Dockerfile创建镜像
  5. php的session实现
  6. sharepoint列表EventHandle的开发 -转
  7. opencv imwrite()函数中 ImwriteFlags 的 cv.IMWRITE_JPEG_RST_INTERVAL(JPEG restart interval 重启间隔)是什么?
  8. 【机器学习】XGBoost学习笔记
  9. C语言课后习题(13)
  10. c语言将数组元素循环右移3位,如何将一个数组的元素循环左移?
  11. 昨天晚上的总结--人跟人的区别在于想的多少吧
  12. 实现一次加载多级所有的菜单
  13. 拓端tecdat|matlab使用Copula仿真优化市场风险数据VaR分析
  14. 关于Exception出现application exception overridden by commit exception
  15. 学习篇-Activiti-29-流程定义存储表
  16. pythonic的典故_旷视开源深度学习框架「天元」,提供人人可用的AI“生产力工具”【星特写】...
  17. Chrome常见黑客插件及用法
  18. Vulkan_Shader_Day02—光照(基础光照_Phong Lighting Model)
  19. 【UCOSii源码解析】任务管理
  20. .net 微信开发

热门文章

  1. 【中南林业科技大学】【陈】第十周作业sqi成绩管理系统
  2. 永洪科技何春涛:中国企业数据技术的6大需求和解决之道
  3. 微软全球最有价值专家(MVP) - 中国区2008年7月最有价值专家名录
  4. HDU:1222wolfnbsp;andnbsp;habbit解题报告
  5. 【Linux】Ubuntu20.04 无法访问 http://cn.archive.ubuntu.com 问题记录解决
  6. 域名系统的主要功能是什么?域名系统中的根服务器和权威服务器有何区别?权威服务器与管辖区有何关系?
  7. linux gpt分区挂载,GPT分区和挂载
  8. KT6368A蓝牙转HID键盘_蓝牙ibeacon模块方案测试板使用说明
  9. OC简介及基本语法(一)
  10. /config.php,app/admin/config.php · 静水流深/wotuoquan - Gitee.com