有没有办法在 Android 中聚焦可跨度文本以实现可访问性



我有一个有多个链接的String。我正在使用SpannableText,除了可访问性回话外,它运行良好。

有什么方法可以提供链接的可访问性吗?

创建一个如下所示的类

package com.go.disney.disneycruise.util
import android.content.Context
import android.graphics.Rect
import android.os.Bundle
import android.text.Layout
import android.text.Spanned
import android.text.style.ClickableSpan
import android.view.accessibility.AccessibilityEvent
import android.widget.TextView
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.customview.widget.ExploreByTouchHelper
import com.disney.wdpro.dlog.DLog
import com.disney.wdpro.support.accessibility.ContentDescriptionBuilder
import kotlin.math.max
import kotlin.math.min
/**
 * An accessibility delegate that allows [ClickableSpan] to be focused and
 * clicked by accessibility services.
 *
 * @author arun.muraleedharan.
 */
class LinkAccessibilityHelper(
    private val linkTextView: TextView,
    private val context: Context,
    private val linkAccessibilityId: String? = null
) : ExploreByTouchHelper(linkTextView) {
    private val viewRect = Rect()
    override fun getVirtualViewAt(x: Float, y: Float): Int {
        val text = linkTextView.text
        if (text is Spanned) {
            val offset = getOffsetForPosition(linkTextView, x, y)
            val linkSpans = text.getSpans(offset, offset, ClickableSpan::class.java)
            if (linkSpans.size == 1) {
                val linkSpan = linkSpans[0]
                return text.getSpanStart(linkSpan)
            }
        }
        return INVALID_ID
    }
    override fun getVisibleVirtualViews(virtualViewIds: MutableList<Int>) {
        val text = linkTextView.text
        if (text is Spanned) {
            val linkSpans = text.getSpans(0, text.length, ClickableSpan::class.java)
            for (span in linkSpans) {
                virtualViewIds.add(text.getSpanStart(span))
            }
        }
    }
    override fun onPopulateEventForVirtualView(virtualViewId: Int, event: AccessibilityEvent) {
        val span = getSpanForOffset(virtualViewId)
        if (span != null) {
            val spannedTextAccessibility = getTextForSpan(span).toString() + LINK
            event.contentDescription = spannedTextAccessibility
        } else {
            DLog.e(TAG, "LinkSpan is null for offset: $virtualViewId")
            event.contentDescription = linkTextView.text
        }
    }
    override fun onPopulateNodeForVirtualView(
        virtualViewId: Int,
        info: AccessibilityNodeInfoCompat
    ) {
        linkAccessibilityId?.let { info.viewIdResourceName = it }
        val span = getSpanForOffset(virtualViewId)
        if (span != null) {
            val contentDescriptionBuilder = ContentDescriptionBuilder(
                context
            )
            contentDescriptionBuilder.append(getTextForSpan(span).toString())
            contentDescriptionBuilder.appendWithSeparator(LINK)
            info.contentDescription = contentDescriptionBuilder.toString()
        } else {
            DLog.e(TAG, "LinkSpan is null for offset: $virtualViewId")
            info.contentDescription = linkTextView.text
        }
        info.isFocusable = true
        info.isClickable = true
        getBoundsForSpan(span, viewRect)
        if (viewRect.isEmpty) {
            DLog.e(TAG, "LinkSpan bounds is empty for: $virtualViewId")
            viewRect[0, 0, 1] = 1
        }
        info.setBoundsInParent(viewRect)
        info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK)
    }
    override fun onPerformActionForVirtualView(
        virtualViewId: Int,
        action: Int,
        arguments: Bundle?
    ): Boolean {
        if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
            val span = getSpanForOffset(virtualViewId)
            if (span != null) {
                span.onClick(linkTextView)
                return true
            } else {
                DLog.e(TAG, "LinkSpan is null for offset: $virtualViewId")
            }
        }
        return false
    }
    private fun getSpanForOffset(offset: Int): ClickableSpan? {
        val text = linkTextView.text
        if (text is Spanned) {
            val spans = text.getSpans(offset, offset, ClickableSpan::class.java)
            if (spans.size == 1) {
                return spans[0]
            }
        }
        return null
    }
    private fun getTextForSpan(span: ClickableSpan): CharSequence {
        val text = linkTextView.text
        if (text is Spanned) {
            return text.subSequence(
                text.getSpanStart(span),
                text.getSpanEnd(span)
            )
        }
        return text
    }
    // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for the
    // section on the first line.
    private fun getBoundsForSpan(span: ClickableSpan?, outRect: Rect): Rect {
        val text = linkTextView.text
        outRect.setEmpty()
        if (text is Spanned) {
            val layout = linkTextView.layout
            if (layout != null) {
                val spanStart = text.getSpanStart(span)
                val spanEnd = text.getSpanEnd(span)
                val xStart = layout.getPrimaryHorizontal(spanStart)
                val xEnd = layout.getPrimaryHorizontal(spanEnd)
                val lineStart = layout.getLineForOffset(spanStart)
                val lineEnd = layout.getLineForOffset(spanEnd)
                layout.getLineBounds(lineStart, outRect)
                if (lineEnd == lineStart) {
                    // If the span is on a single line, adjust both the left and right bounds
                    // so outrect is exactly bounding the span.
                    outRect.left = min(xStart, xEnd).toInt()
                    outRect.right = max(xStart, xEnd).toInt()
                } else {
                    // If the span wraps across multiple lines, only use the first line (as returned
                    // by layout.getLineBounds above), and adjust the "start" of outrect to where
                    // the span starts, leaving the "end" of outrect at the end of the line.
                    // ("start" being left for LTR, and right for RTL)
                    if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
                        outRect.right = xStart.toInt()
                    } else {
                        outRect.left = xStart.toInt()
                    }
                }
                // Offset for padding
                outRect.offset(linkTextView.totalPaddingLeft, linkTextView.totalPaddingTop)
            }
        }
        return outRect
    }
    companion object {
        private const val TAG = "LinkAccessibilityHelper"
        const val LINK = "link."
        // Compat implementation of TextView#getOffsetForPosition().
        private fun getOffsetForPosition(view: TextView, x: Float, y: Float): Int {
            if (view.layout == null) {
                return -1
            }
            val line = getLineAtCoordinate(view, y)
            return getOffsetAtCoordinate(view, line, x)
        }
        private fun convertToLocalHorizontalCoordinate(view: TextView, x: Float): Float {
            var coordinateX = x
            coordinateX -= view.totalPaddingLeft.toFloat()
            // Clamp the position to inside of the view.
            coordinateX = max(0.0f, coordinateX)
            coordinateX = min((view.width - view.totalPaddingRight - 1).toFloat(), coordinateX)
            coordinateX += view.scrollX.toFloat()
            return coordinateX
        }
        private fun getLineAtCoordinate(view: TextView, y: Float): Int {
            var coordinateY = y
            coordinateY -= view.totalPaddingTop.toFloat()
            // Clamp the position to inside of the view.
            coordinateY = max(0.0f, coordinateY)
            coordinateY = min((view.height - view.totalPaddingBottom - 1).toFloat(), coordinateY)
            coordinateY += view.scrollY.toFloat()
            return view.layout.getLineForVertical(coordinateY.toInt())
        }
        private fun getOffsetAtCoordinate(view: TextView, line: Int, x: Float): Int {
            var coordinateX = x
            coordinateX = convertToLocalHorizontalCoordinate(view, coordinateX)
            return view.layout.getOffsetForHorizontal(line, coordinateX)
        }
    }
}

之后像下面这样实现它

ViewCompat.setAccessibilityDelegate(
                textView, LinkAccessibilityHelper(
                    textView, context, "linkId"
                )
            )

相关内容

  • 没有找到相关文章

最新更新