国家近期开展了无障碍建设活动。为了积极响应国家号召,为抖音视障用户能够得到更好的交互体验,对抖音无障碍功能进行了专项治理和改造。
抖音的无障碍功能实现主要是通过开启 Google TalkBack(或第三方屏幕阅读)功能,将用户在屏幕上触摸选中区域的内容朗读出来,使得视障人士可以根据朗读的内容获取自己当前操作区域的信息,从而提升视障人士的使用和交互体验。
常用的操作手势:
使研发同学对无障碍功能有一个更加全面的认识和了解,方便研发同学进行无障碍功能的开发。
本文将分为无障碍功能实现原理和无障碍功能实现实例两部分进行介绍。
无障碍功能的实现需要以下三个部分的支持:辅助 App(例如 TalkBack)、被辅助 app(用户使用的 app,例如抖音头条等)以及系统服务 AccessibilityManagerService,这三者之间的关系如下图所示:
从上图中可以看出,以上的流程主要涉及到三个进程的通信。辅助 app 和被辅助 app 不需要直接跟被辅助的 app 通信,而是通过 SystemServer 进行中转通信,这个过程主要涉及到了四个 aidl 接口:
当被辅助 app 产生触摸事件后,会通过该接口发送无障碍事件给 SystemServer 进程的 AccessibilityManagerService。
当 SystemServer 接收到被辅助 app 发送的无障碍事件时,会将事件通过该接口传递给辅助 app(例如 TalkBack)进行处理。
当需要被辅助 app 的某个 View 的信息时,可以通过这两个接口的 findAccessibilityNodeInfosByViewId 方法实现。
当用户触摸屏幕时,会经过以下的流程将触摸事件传递给被触摸的 View:
下面本文将主要分析以上流程中四个重点部分的内容:无障碍模式下的事件转换、触摸事件到 Activity 的传递过程、事件传递给具体的 View 的分发过程以及最终无障碍事件的执行流程。
在 TalkBack 开启的状态下,由于 TalkBack 的无障碍服务中声明了 android:canRequestTouchExplorationMode=''true'' ,因此开启 TalkBack 后 AccessibilityManagerService 会更新 AccessibilityInputFilter 的FLAG_FEATURE_TOUCH_EXPLORATION
(触摸浏览)属性置为 true。
在 FLAG_FEATURE_TOUCH_EXPLORATION 模式下会创建一个 TouchExplorer 对象。AccessibilityInputFilter 继承了 InputFilter,对输入事件进行过滤,通过和 TouchExplorer 共同实现 TalkBack 模式下的触摸浏览手势。TouchExplorer 负责将普通触摸事件转换为触摸浏览手势,例如将 MotionEvent.ACTION_DOWN 事件转换为 MotionEvent.ACTION_HOVER_ENTER(悬停事件)。因此在 TalkBack 开启的情况下,用户单击 View 时,App 执行的是 ACTION_HOVER_ENTER 事件,双击 View 时才会执行 ACTION_DOWN 事件。
在 Android 中,消息机制是 handler 机制,通过将消息封装到 Message 中,并将该消息发送到 handler 所在的 MessageQueue 中,通过 Looper 不断调用 MessageQueue 的 next 方法进行消息的处理。
当用户触摸屏幕上的某个 View 时,handler 会对收到的消息进行以下的处理:
这里需要重点看一下 View 的 dispatchPointerEvent() 方法:
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
在该方法中对 event 进行判断,如果是 touchEvent 就调用 dispatchTouchEvent() 方法,否则调用 dispatchGenericMotionEvent() 方法。判断是否为 touch 事件的逻辑如下:
bool MotionEvent::isTouchEvent(int32_t source, int32_t action) {
if (source & AINPUT_SOURCE_CLASS_POINTER) {
// Specifically excludes HOVER_MOVE and SCROLL.
switch (action & AMOTION_EVENT_ACTION_MASK) {
case AMOTION_EVENT_ACTION_DOWN:
case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_POINTER_DOWN:
case AMOTION_EVENT_ACTION_POINTER_UP:
case AMOTION_EVENT_ACTION_CANCEL:
case AMOTION_EVENT_ACTION_OUTSIDE:
return true;
}
}
return false;
}
符合以上 case 的 event 即为 TouchEvent。
首先来看一下 dispatchPointerEvent 方法中对 TouchEvent 事件的处理,进入 DecorView 的 dispatchTouchEvent() 方法中:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
在该方法中,mWindow 是与 Activity 关联的 PhoneWindow 对象,由于 DecorView 是由 PhoneWindow 创建的,并且通过 setWindow() 方法,DecoView 对象持有 PhoneWindow 对象的引用。通过 getCallback() 方法,获得了实现了 Window.Callback 的对象,而 Activity 实现了这个接口,因此当调用cb.dispatchTouchEvent(ev) 时,实际上调用的是 Activity 中的 dispatchTouchEvent() 方法。
同样的在 dispatchGenericMotionEvent() 方法中,也有类似的代码逻辑:
@Override
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchGenericMotionEvent(ev) : super.dispatchGenericMotionEvent(ev);
}
此方法中实际上也是调用了 Activity 的 dispatchGenericMotionEvent() 方法对事件进行后续的分发和处理。此时事件就已经传递到了 Activity,由 Activity 进一步进行事件分发。
在研究无障碍模式下的事件传递过程之前,首先来回顾一下普通模式下的事件传递机制:
3.1.1 普通模式下事件分发 Key Method
当一个 MotionEvent 产生之后,系统需要将该事件传递给一个具体的 view,这个传递过程就是事件的分发过程。分发过程依赖于以下三个重要方法:
该方法用来进行事件的分发,方法的返回值取决于当前 View 的 onTouchEvent() 方法和子 View 的 dispatchTouchEvent() 方法的影响。
仅 ViewGroup 拥有的方法,用来判断是否拦截某个事件。
在 dispatchTouchEvent() 方法中进行调用,用来处理点击事件。
3.1.2 普通模式下的事件分发
整个分发过程可以用以下的流程图来表示:
无障碍模式下的事件分发与普通模式下的事件分发有很多相似之处:
3.2.1 无障碍模式下的事件分发 Key Method:
与普通事件触摸事件的分发类似,无障碍事件触发事件分发也有类似的三个重要方法:
该方法用来进行事件的分发,方法的返回值取决于当前 View 的 onHoverEvent() 方法和子 View 的 dispatchHoverEvent() 方法的影响。
仅 ViewGroup 拥有的方法,用来判断是否拦截某个事件。
在 dispatchHoverEvent() 方法中进行调用,用来处理 hover 事件。
3.2.2 无障碍模式下的事件分发
当用户处于无障碍模式下,用户进行点击屏幕时,会调用 dispatchPointerEvent 方法中的 dispatchGenericMotionEvent 方法:
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
实际上调用的是 Activity 的 dispatchGenericMotionEvent() 方法,Activity 接收到事件后,会传递给 PhoneWindow 再传递给 DecorView。DecorView 会调用 View 的 dispatchGenericMotionEvent() 方法:
public boolean dispatchGenericMotionEvent(MotionEvent event) {
···
final int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
final int action = event.getAction();
//判断事件类型属于Hover,调用dispatch方法开始进行分发
if (action == MotionEvent.ACTION_HOVER_ENTER
|| action == MotionEvent.ACTION_HOVER_MOVE
|| action == MotionEvent.ACTION_HOVER_EXIT) {
if (dispatchHoverEvent(event)) {
return true;
}
}
...
return false;
}
在该方法中,如果判断事件为 HoverEvent,就调用 ViewGroup 的 dispatchHoverEvent() 方法开始进行事件分发。
如果某个 ViewGroup 的 onInterceptHoverEvent() 方法返回 true,表示它要拦截当前事件,并交给自己处理,反之返回 false 表示不拦截当前事件,并将当前事件继续传递给子 View,子 View 会调用自己的 dispatchHoverEvent() 方法,如此循环往复直到事件最终被处理。
在事件处理阶段,View/ViewGroup 首先会判断是否设置了 OnHoverListener,并判断它的 onHover 方法的返回值是否为 true,如果返回值为 true,则不会调用 onHoverEvent() ,反之会调用 onHoverEvent() 方法对事件进行处理。
整个处理过程可以用下面的流程图进行表示:
在 onHoverEvent() 方法中,会调用到 sendAccessibilityHoverEvent()方法,该方法后续会调用以下方法:
以上 6 种方法为当自定义 View 时适配无障碍模式可以覆盖实现的方法,可以重写 View 的这些方法或者实现 View.AccessibilityDelegate 来解决一些特殊场景下 TalkBack 播报的问题。
其中的 sendAccessibilityEventUnchecked 方法会向上传递到 ViewRootImpl 的 requestSendAccessibilityEvent 方法中,从堆栈信息中就可以证实这一点:
接着无障碍事件会通过 AccessibilityManager 的 sendAccessibilityEvent 方法跨进程调用 system_process 进程的 AccessibilityManagerService,将 AccessibilityEvent 事件传递到 TalkBack 的 TalkBackService 中。
这一节主要分析从 TalkBack 发出无障碍事件,到被辅助 app 在屏幕上绘制出绿框的过程。
TalkBack 将无障碍事件发送给被辅助 APP 时,需要 system_process 进程作为中转,对应的接口为 IAccessibilityServiceConnection.aidl 和 IAccessibilityInteractionConnection.aidl。经过中转后,最终会调用到被触摸 View 的 performAccessibilityAction 方法中,在没有 delegate 的情况下,会执行 performAccessibilityActionInternal 方法。在该方法中,如果是 ACTION_ACCESSIBILITY_FOCUS 事件,会执行 requestAccessibilityFocus 方法:
这个方法会执行两个关键操作:
2 . 调用 sendAccessibilityEvent 方法,将 TYPE_VIEW_ACCESSIBILITY_FOCUSED 事件发送出去,这个事件被 talkback 接收后,会调用朗读引擎 TTS 读出 View 的内容,实现了无障碍模式下对触摸区域内容的播报。
解决方案:在该 View 的 android:contentDescription 属性上设置需要播报的 String。
解决方案:将不需要播报的 View 的 android:importantForAccessibility 属性设置为 no,将需要播报的 View 的该属性设置为 yes。
解决方案:将下层的根 View 的 android:importantForAccessibility 属性设置为"noHideDescendants"
解决方案:在自定义 Toast 展示的时候,主动发送一个 AccessibilityEvent 事件
mText.postDelayed(new Runnable() {
@Override public void run() {
mText.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
}
}, 1);
设置延时是为了避免不生效的问题。
解决方法:override View 的 onPopulateAccessibilityEvent()方法。
举例:设置自定义 View 开/关状态(已开启/已关闭)的播报内容。
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
final CharSequence text = isChecked() ? "已开启" : "已关闭";
if (text != null) {
event.getText().add(text);
}
}
解决方法:使用 AccessibilityDelegate
ViewCompat.setAccessibilityDelegate(targetView, new AccessibilityDelegateCompat() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.setRoleDescription("标签类型");//设置播报的标签类型
info.setCheckable(true);
info.setChecked(checked);//设置播报的被选中状态
}
});
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/2JBMfaV4oa0XLrJ-55sPdg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。