Android 手势导航(Launcher3 部分)
手势导航功能的实现主要由 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 部分)相关推荐
- Android 手势导航(从下往上滑动进入多任务页面)
Android系统启动篇 1,<android系统启动流程简介> 2,<android init进程启动流程> 3,<android zygote进程启动流程> 4 ...
- recyclerview 滚动冲突_如何处理手势冲突 | 手势导航连载 (三)
作者 / Chris Banes, Android 开发者关系团队工程师 我们将在近期为大家带来一个关于 "手势导航" 的系列连载,本文是手势导航连载的第三篇,如果您希望查看前两篇 ...
- 开启全面屏体验 | 手势导航 (一)
作者 / Chris Banes, Android 开发者关系团队工程师 本文是手势导航连载的第一篇文章,在接下来的时间里,我们将会为大家带来一系列手势导航的话题,敬请关注! 我们在 Android ...
- Android 10 手势导航源码分析
Android O之前的虚拟按键,基本的控制方法都是在SystemUI中做处理的,在Android 10上为了在手势导航操作时其动画更加流畅,与Launcher互动效果更好,google的设计师就把手 ...
- android studio 顶部导航栏_Android10 手势导航开发与处理:边到边(I)
这是我们有关"手势导航"系列的第一篇文章. 借助Android 10,已添加了新的系统导航模式,允许用户向后导航,导航至主屏幕并通过手势触发设备助手. Android 10 中新手 ...
- Android 11.0 自定义仿小米全面屏手势导航左右手势滑动返回UI效果
目录 1.概述 2.自定义仿小米全面屏手势导航返回ui布局的核心代码 3.自定义左右手势返回UI样式的核心代码功能分析 3.1 NavigationBarView手势导航布局左右手势返回的相关代码 3 ...
- 如何在任何Android手机上获取手势导航
Android's upcoming iteration (currently just called "P") contains a new gesture navigation ...
- android之适配华为手机手势导航方式
高仿滴滴首页滑动布局 做这个的时候需要计算出首页底部布局距离顶部的高度,要把虚拟导航栏高度算在里面 但是发现了一个华为的奇葩bug,华为emui系统的系统导航里可以选手势导航和虚拟导航等方式 选用手势 ...
- android 按钮手势,如何在Android 10中使用手势导航或如何关闭它
谷歌移动操作系统的最新版本Android 10带有许多出色的新功能.在Android 9.0 Pie中向Android中引入了手势导航 - 它使用滑动和其他操作来控制手机,而不是点击按钮.在Andro ...
- Android 12默认手势导航及bug修复
Android 12默认手势导航方法有2种 第1种方法:配置如下ro即可 ro.boot.vendor.overlay.theme=com.android.internal.systemui.navb ...
最新文章
- Tensorflow |(5)模型保存与恢复、自定义命令行参数
- 新电脑装win7_微软正式跟Win7系统告别了!国产电脑操作新系统诞生:系统更加美观...
- 信息太多,时间太少: 大脑如何区分重要和不重要的事?
- [转]Docker基础-使用Dockerfile创建镜像
- php的session实现
- sharepoint列表EventHandle的开发 -转
- opencv imwrite()函数中 ImwriteFlags 的 cv.IMWRITE_JPEG_RST_INTERVAL(JPEG restart interval 重启间隔)是什么?
- 【机器学习】XGBoost学习笔记
- C语言课后习题(13)
- c语言将数组元素循环右移3位,如何将一个数组的元素循环左移?
- 昨天晚上的总结--人跟人的区别在于想的多少吧
- 实现一次加载多级所有的菜单
- 拓端tecdat|matlab使用Copula仿真优化市场风险数据VaR分析
- 关于Exception出现application exception overridden by commit exception
- 学习篇-Activiti-29-流程定义存储表
- pythonic的典故_旷视开源深度学习框架「天元」,提供人人可用的AI“生产力工具”【星特写】...
- Chrome常见黑客插件及用法
- Vulkan_Shader_Day02—光照(基础光照_Phong Lighting Model)
- 【UCOSii源码解析】任务管理
- .net 微信开发
热门文章
- 【中南林业科技大学】【陈】第十周作业sqi成绩管理系统
- 永洪科技何春涛:中国企业数据技术的6大需求和解决之道
- 微软全球最有价值专家(MVP) - 中国区2008年7月最有价值专家名录
- HDU:1222wolfnbsp;andnbsp;habbit解题报告
- 【Linux】Ubuntu20.04 无法访问 http://cn.archive.ubuntu.com 问题记录解决
- 域名系统的主要功能是什么?域名系统中的根服务器和权威服务器有何区别?权威服务器与管辖区有何关系?
- linux gpt分区挂载,GPT分区和挂载
- KT6368A蓝牙转HID键盘_蓝牙ibeacon模块方案测试板使用说明
- OC简介及基本语法(一)
- /config.php,app/admin/config.php · 静水流深/wotuoquan - Gitee.com