Android 手势导航(Launcher3 部分)

2024-04-20 22:48

本文主要是介绍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 部分)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921523

相关文章

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.

Android Studio 配置国内镜像源的实现步骤

《AndroidStudio配置国内镜像源的实现步骤》本文主要介绍了AndroidStudio配置国内镜像源的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、修改 hosts,解决 SDK 下载失败的问题二、修改 gradle 地址,解决 gradle

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

Android WebView无法加载H5页面的常见问题和解决方法

《AndroidWebView无法加载H5页面的常见问题和解决方法》AndroidWebView是一种视图组件,使得Android应用能够显示网页内容,它基于Chromium,具备现代浏览器的许多功... 目录1. WebView 简介2. 常见问题3. 网络权限设置4. 启用 JavaScript5. D