You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:18:21