Android 11(API30) TalkBack12.2下ClickableSpan点击无响应问题
问题描述
我实现了一个包含ClickableSpan的自定义View,在Android 11(API 30)设备上运行TalkBack 12.2版本时,进入TalkBack菜单的链接列表点击对应链接后无任何响应,预期点击后应当弹出Toast提示。现想确认该问题是否属于系统层面Bug、是否需要向Google提交反馈。
相关实现代码
myview.java
package ru.alexandrkozlovskiy.test; import android.content.Context; import android.os.Bundle; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ClickableSpan; import android.util.AttributeSet; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; class myview extends View { public myview(Context context) { super(context); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } public myview(Context context, AttributeSet attrs) { super(context, attrs); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } public myview(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } public myview(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); SpannableStringBuilder sb= new SpannableStringBuilder("Test of clickable span"); sb.setSpan(new ClickableSpan() { @Override public void onClick(View v) { try { Toast.makeText(getContext(),"Test of clickable span",1).show(); } catch (Exception e) { info.setContentDescription(e.toString()); setContentDescription(e.toString()); } } }, 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); info.setText(sb); } }
MainActivity.java
package ru.alexandrkozlovskiy.test; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; import android.graphics.Rect; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.widget.Button; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { public myview tv; public LinearLayout c; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = findViewById(R.id.tv); c=findViewById(R.id.c); } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/c" tools:context=".MainActivity"> <ru.alexandrkozlovskiy.test.myview android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/tv"/> </LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ru.alexandrkozlovskiy.test"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
问题结论
这不是系统Bug,没必要给Google提反馈,问题出在实现不符合Android无障碍交互规范:
- 直接继承了最基础的
View类,只在onInitializeAccessibilityNodeInfo方法里给无障碍节点塞了带ClickableSpan的文本,既没有把每个可点击span映射成独立的无障碍虚拟节点,也没有处理TalkBack发来的点击动作回调。原生TextView自带ClickableSpan的无障碍适配逻辑,会自动完成节点映射、事件分发,自定义View缺了这整套逻辑,TalkBack从链接列表点进来的时候根本找不到对应的执行入口,自然不会有反应。 - 代码里的
ClickableSpan是每次onInitializeAccessibilityNodeInfo回调时临时创建的,和View本身的触摸事件、绘制逻辑完全没绑定,就算不用TalkBack,普通用户直接点View上对应位置的文本,这个span的点击逻辑也不会触发,本身功能就没做完整。
修复方案
- 没有特殊自定义绘制需求的话,直接继承
AppCompatTextView实现,把带ClickableSpan的文本通过setText设置给TextView,再调用setMovementMethod(LinkMovementMethod.getInstance())即可,原生组件已经把所有无障碍逻辑处理好了,TalkBack链接列表的点击可以正常触发Toast。 - 必须继承基础
View做自定义绘制的话,需要自己实现AccessibilityNodeProvider,把每个ClickableSpan对应的文本区域作为虚拟子节点暴露给无障碍服务,同时重写performAccessibilityAction方法,监听到虚拟节点的点击动作时主动调用对应span的onClick逻辑。
内容的提问来源于stack exchange,提问作者alexandr kozlovskiy
相关产品推荐
相关产品推荐

