Android 11手势的设计与实现
Android R虚拟按键的设计与实现一文中我们详细的讲解了android操作系统中的虚拟按键实现,所有逻辑都是在SystemUi中,然后通过事件注入的方式来实现最近任务、返回、HOME等按键。
这篇文章我们详细来分析android操作系统中的手势是如何实现返回、MENU、HOME按键的。为了用户交互的动画效果手势中的HOME和MENU不再是利用注入按键来实现,而是直接放在Lanucher3中实现,返回桌面手势和启动最近任务手势出现后,会在Lanucher中直接启动对应的最近任务界面,从而使得手势交互更加流畅可以取消。返回键还是沿用了虚拟按键的原理,通过注入KEYCODE_BACK来实现,只不过时操作方式不同而已。
总结:手势的设计并不需要判断用户是否按下后向上滑动或者左右滑动,而是当我们按下处于这块区域时,此处的窗口就得到了事件从而跟随手指改变,并根据最后抬手的状态做出最终的处理。
我们知道手势是在桌面Lanucher启动后才会生效的。因此我们需要先分析SystemUI和Lanucher是如何启动的,这样我们才能知道手势是如何初始化的。
android手机开机后我们都知道先进入的就是锁屏界面,因此此时SystemUI已经启动,我们看一下SystemUi在系统中的配置,在SystemUi的应用配置文件中frameworks\base\packages\SystemUI\AndroidManifest.xml中是这样定义的。
<application
android:name=".SystemUIApplication"
android:persistent="true"
android:allowClearUserData="false"
android:allowBackup="false"
android:hardwareAccelerated="true"
android:label="@string/app_label"
android:icon="@drawable/icon"
android:process="com.android.systemui"
android:supportsRtl="true"
android:theme="@style/Theme.SystemUI"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
tools:replace="android:appComponentFactory"
android:appComponentFactory=".SystemUIAppComponentFactory">
其中有个很重要的属性android:directBootAware="true",该属性在开发者文档中是这样解释的,如下图:
这个属性对于手势导航的启动是有决定性的作用,有了这属性,其无论是应用还是服务都能第一时间启动,它保证了手势导航功能在系统准备好之前做好初始化。
这篇文章中就不讲SystemUI中的base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java的加载了,在https://blog.csdn.net/chen364567628/article/details/107525744
这篇文章中以及详细讲解了,请看加载流程。我们主要看的是在NavigationBarFragment的构造方法中需要创建一个OverviewProxyService对象,该对象用于后续和Lanucher建立通道。
在OverviewProxyService的构造方法中监听了Lanucher的改变。
紧接着就开始连接Lanucher
此处还做了个连接重试。
此处连接的是private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";这个服务,查询源码发现,该服务被写在Lanucher中,我们来看下,具体的配置。
在Lanucher中action为android.intent.action.QUICKSTEP_SERVICE的服务是TouchInteractionService,这个服务也设置了属性android:directBootAware="true"保证该服务的启动。
<service
android:name="com.android.quickstep.TouchInteractionService"
android:permission="android.permission.STATUS_BAR_SERVICE"
android:directBootAware="true" >
<intent-filter>
<action android:name="android.intent.action.QUICKSTEP_SERVICE" />
intent-filter>
service>
在上面我们还看到当服务连接成功后调用了onInitialize方法将systemUI的对象传递给了Lanucher,这样保证了SystemUI和lanucher两个不同进程的交互。mISystemUiProxy就是SystemUi在Lanucher的对象。
接着我们看Lanucher中的TouchInteractionService服务。
同时,在onInitialize中初始化并注册了手势窗口的通道swipe-up。保证了事件能够传递给Lanucher,在此处要注意Android原生的系统中,手势区域是非透明的,因此此区域内触摸都将会被认为是手势。
此处注册了通道后事件就会会通过onInputEvent方法回调给TouchInteractionService服务。接下来看lanucher中对手势事件的处理
在Lanucher的方法中,当第一个手指的动作是DOWN的时候,判断这个点是否在mSwipeSharedState的矩形区域中
如果在这个区域中那么就会执行newConsumer的来创建一个事件消费者,else if则是判断此处是不是点击Google的语音助手的窗口,如果存在就给语音助手。防止事件被丢弃,如果都不是则丢弃事件不处理。此处详细分析手势所在逻辑的位置newConsumer,在newConsumer方法中除了创建特殊Consumer的逻辑外,我们主要看newBaseConsumer,手势真正的消费者在此处。
此处创建了多个消费者,根据复杂的场景,如下图:
相关的消费者如下,Google又如下的注释
AssistantTouchConsumer:
Touch consumer for handling events to launch assistant from launcher
AccessibilityInputConsumer:
Touch consumer for two finger swipe actions for accessibility actions
DeviceLockedInputConsumer:
A dummy input consumer used when the device is still locked, e.g. from secure camera.
FallbackNoButtonInputConsumer:
In case of 3P launcher, swipe-up will go to Launcher and there will be no way to reach overview
OtherActivityInputConsumer:
Input consumer for handling events originating from an activity other than Launcher
OverviewInputConsumer:
Input consumer for handling touch on the recents/Launcher activity.
OverviewWithoutFocusInputConsumer:
Using a separate InputConsumer when Launcher is resumed but not focused. When Launcher is not focused and we try to dispatch events to Launcher, it can lead to a inconsistent state. For eg, NavBarTouchController was trying to take launcher from NORMAL to NORMAL state causing the endCallback to be called immediately, which in turn didn't clear Swipedetetor state.
ResetGestureInputConsumer:
A NO_OP input consumer which also resets any pending gesture
Bypass systemstate check when using shared state
System state can change using the interaction, for eg: an app can enter immersive mode in the middle of quickswitch. Ignore such changes to prevent system gestures getting blocked by an app
Fixing nullpointer in device locked consumer construction when user is not locked yet Creating a fallback resetGesture input consumer, which cancels any pending transition in case we missed to cancel it
ScreenPinnedInputConsumer:
An input consumer that detects swipe up and hold to exit screen pinning mode.
我中点讲解OverviewInputConsumer启动最近任务,如下图创建Consumer,并获取控制者,此处分为聚焦和不聚焦。OverviewWithoutFocusInputConsumer不做分析代码简单。
相关控制者如下:
FlingAndHoldTouchController
Touch controller which handles swipe and hold to go to Overview
NavBarToHomeTouchController
Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
OverviewToAllAppsTouchController
Touch controller from going from OVERVIEW to ALL_APPS.This is used in landscape mode. It is also used in portrait mode for the fallback recents.
QuickSwitchTouchController
Handles quick switching to a recent task from the home screen.
TaskViewTouchController
Touch controller for handling task view card swipes
接着上述讲,OverviewInputConsumer如下构造方法中只是做了些变量初始化
细心的人已经发现了事件其实是在newConsumer中给了消费者的onMotionEvent。
在onMotionEvent中就是关闭系统的对话框等,同时在此处有个getDragLayer,此处拿到的就是动画的主体BaseDragLayer从而可以拖拽
具体事件处理如下: