Android 手势导航(从下往上滑动进入多任务页面)
Android系统启动篇
1,《android系统启动流程简介》
2,《android init进程启动流程》
3,《android zygote进程启动流程》
4,《Android SystemServer进程启动流程》
5,《android launcher启动流程》
6,《Android Activity启动过程详解》
Android系统开发准备篇
1,《Android 源码下载和编译》
2,《android 11源码编译和pixel3 刷机》
3,《Android Framework代码IDE加载和调试》
Android系统开发实践篇
1,《android设置默认输入法》
2,《android framework预制APK应用》
3,《Android系统层面限制应用开机自启动详解》
4,《android单独编译framework模块并push》
5,《Android Framework开发系统问题分析》
Android系统开发核心知识储备篇
1,《Android编译系统-envsetup和lunch代码篇》
2,《Android编译系统-概念篇》
3,《android日志系统详解》
4,《Android系统Handler详解》
5,《Android系统Binder详解》
6,《Android中Activity、View和Window关系详解》
7,《android view绘制流程详解》
8,《Android读取系统属性详解》
9,《android 窗口管理机制详解》
10,《初识Android系统》
11,《android中AMS进程通知Zygote进程fork新进程的通信方式》
Android核心功能详解篇
1,《android应用市场点击下载APK安装详解》
2,《Android 手势导航(从下往上滑动进入多任务页面)》
3,《android手势分析(应用界面左往右边滑动退出应用)》
4,《android应用安装流程详解》
5,《android11安装应用触发桌面图标刷新流程》
6,《Android系统多任务Recents详解》
7,《android系统导航栏视图分析》
———————————————————————————————————————————
手势导航功能的实现主要由 SystemUI + Launcher3 共同处理,SystemUI 中主要由 OverviewProxyService.java 监听,而在 Launcher3 中启动一个 TouchInteractionService 服务监听,主要代码实现都由 Launcher 中处理。
Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.javaprivate 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.javapublic 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.javapublic 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 进行判断最终的手势滑动动作是哪种。
系统设置有四种手势动作:
1,HOME 回到主界面
2,RECENTS 多任务界面
3,NEW_TASK 切换到新的应用
4,LAST_TASK 仍然停留在当前界面
手势动作,从底部往上滑,启动RECENTS 多任务界面
动画效果会让背景页面可见,回到luncher3页面,加载QuickstepLauncher,其中LauncherRecentsView加载应用的容器。
Android 手势导航(从下往上滑动进入多任务页面)相关推荐
- Android从上往下滑动或从下往上滑动结束Activity
之前有看过xiaanming写的侧滑返回,于是仿照他的Demo,写了这个从上往下滑动或者从下往上滑动结束Activity 先附图一张,由于这台电脑分辨率有问题以及模拟器的缘故,先凑活看吧 先贴代码: ...
- android studio 顶部导航栏_Android10 手势导航开发与处理:边到边(I)
这是我们有关"手势导航"系列的第一篇文章. 借助Android 10,已添加了新的系统导航模式,允许用户向后导航,导航至主屏幕并通过手势触发设备助手. Android 10 中新手 ...
- recyclerview 滚动冲突_如何处理手势冲突 | 手势导航连载 (三)
作者 / Chris Banes, Android 开发者关系团队工程师 我们将在近期为大家带来一个关于 "手势导航" 的系列连载,本文是手势导航连载的第三篇,如果您希望查看前两篇 ...
- android 虚拟导航按钮(NavigationBar)可手动隐藏开发
NavigationBar可以手动隐藏,随着华为荣耀手机有了这个特点后,目前有很多android手机都有该特性.如下截图所示: 上图的底部虚拟导航按钮的左.右边有两个按钮点击这个按钮,虚拟按钮就会消失 ...
- 开启全面屏体验 | 手势导航 (一)
作者 / Chris Banes, Android 开发者关系团队工程师 本文是手势导航连载的第一篇文章,在接下来的时间里,我们将会为大家带来一系列手势导航的话题,敬请关注! 我们在 Android ...
- 如何在任何Android手机上获取手势导航
Android's upcoming iteration (currently just called "P") contains a new gesture navigation ...
- 【Android 手势冲突】Colin带你彻底解决RecyclerView与ScrollView滑动冲突问题,并实现RecyclerView悬停导航栏(附demo哦)
在新一期的需求中,产品要求我们做出和美团某个页面类似的功能,即一个页面包含在scrollView中,上面一个部分放置一些常用的广告banner.宫格tab等,下面放置一个RecyclerView用于展 ...
- Android 11.0 自定义仿小米全面屏手势导航左右手势滑动返回UI效果
目录 1.概述 2.自定义仿小米全面屏手势导航返回ui布局的核心代码 3.自定义左右手势返回UI样式的核心代码功能分析 3.1 NavigationBarView手势导航布局左右手势返回的相关代码 3 ...
- Android 12.0 自定义仿小米全面屏手势导航左右手势滑动返回UI效果
目录 1.概述 2.自定义仿小米全面屏手势导航左右手势滑动返回UI效果的核心类
最新文章
- Teradata推出Vantage on Azure,可实现自助配置、快速部署,提供安全、可扩展的高性能分析
- 在tomcat下创建和发布WEB应用
- 想学python看什么书-请问想学python和JAVA得看什么书?
- C#串口上位机软件--IOT串口调试精灵
- 1、C语言面试笔试---变量定义和声明
- Windows上使用gitbook制作电子书
- android 手机wifi重启,android – 如何通过重启来记住wifi配置和连接网络
- HTML与CSS基础之伪元素(五)
- 分类(二):基于向量空间模型的文本分类
- Java 7:如何编写非常快速的Java代码
- .net平台的MongoDB使用
- php登录处理代码,php登录与退出登录实例代码
- UI设计插画素材|移动设备和网络屏幕
- 一些图像处理函数用法
- Artstudio Pro Mac(绘图与图片编辑软件)特别版
- [刷ROM] 一米ROM_V3.1版,4.04自用终极珍藏美化版
- JDK的Proxy技术实现AOP,InvocationHandler和Proxy详解——Spring AOP(三)
- PCB表面贴片元件的手工焊接技巧
- pytorch骚操作之梯度累加,变相增大batch size
- 一年级课程表(4月18日-4月22日)