@@ -162,7 +162,7 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
162162 val x = event.x.toInt()
163163 val y = event.y.toInt()
164164
165- val clickableSpan = getClickableSpanInCoords (x, y)
165+ val clickableSpan = getSpanInCoords (x, y, ClickableSpan :: class .java )
166166
167167 if (clickableSpan == null ) {
168168 clearSelection()
@@ -182,33 +182,53 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
182182 return true
183183 }
184184
185- /* *
186- * Get the clickable span that is at the exact coordinates
187- *
188- * @param x x-position of the click
189- * @param y y-position of the click
190- * @return a clickable span that's located where the click occurred, or: `null` if no clickable
191- * span was located there
192- */
193- private fun getClickableSpanInCoords (x : Int , y : Int ): ClickableSpan ? {
185+ private fun <T > getSpanInCoords (x : Int , y : Int , clazz : Class <T >): T ? {
194186 val offset = getTextOffsetAt(x, y)
195187 if (offset < 0 ) {
196188 return null
197189 }
198190
199191 val spanned = text as ? Spanned ? : return null
200192
201- val clickableSpans = spanned.getSpans(offset, offset, ClickableSpan ::class .java)
202- if (clickableSpans.isNotEmpty()) {
203- return clickableSpans[0 ]
193+ val spans = spanned.getSpans(offset, offset, clazz)
194+ if (spans.isEmpty()) {
195+ return null
196+ }
197+
198+ // When we have multiple spans marked with SPAN_EXCLUSIVE_INCLUSIVE next to each other, both
199+ // spans are returned by getSpans
200+ check(spans.size <= 2 )
201+ for (span in spans) {
202+ val spanFlags = spanned.getSpanFlags(span)
203+ val inclusiveStart =
204+ if ((spanFlags and Spanned .SPAN_INCLUSIVE_INCLUSIVE ) != 0 ||
205+ (spanFlags and Spanned .SPAN_INCLUSIVE_EXCLUSIVE ) != 0 ) {
206+ spanned.getSpanStart(span)
207+ } else {
208+ spanned.getSpanStart(span) + 1
209+ }
210+ val inclusiveEnd =
211+ if ((spanFlags and Spanned .SPAN_INCLUSIVE_INCLUSIVE ) != 0 ||
212+ (spanFlags and Spanned .SPAN_EXCLUSIVE_INCLUSIVE ) != 0 ) {
213+ spanned.getSpanEnd(span)
214+ } else {
215+ spanned.getSpanEnd(span) - 1
216+ }
217+
218+ if (offset >= inclusiveStart && offset <= inclusiveEnd) {
219+ return span
220+ }
204221 }
205222
206223 return null
207224 }
208225
209226 private fun getTextOffsetAt (x : Int , y : Int ): Int {
227+ val layoutX = x - paddingLeft
228+ val layoutY = y - (paddingTop + (preparedLayout?.verticalOffset?.roundToInt() ? : 0 ))
229+
210230 val layout = preparedLayout?.layout ? : return - 1
211- val line = layout.getLineForVertical(y )
231+ val line = layout.getLineForVertical(layoutY )
212232
213233 val left: Float
214234 val right: Float
@@ -238,12 +258,12 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
238258 right = if (rtl) layout.getParagraphRight(line).toFloat() else layout.getLineMax(line)
239259 }
240260
241- if (x < left || x > right) {
261+ if (layoutX < left || layoutX > right) {
242262 return - 1
243263 }
244264
245265 return try {
246- layout.getOffsetForHorizontal(line, x .toFloat())
266+ layout.getOffsetForHorizontal(line, layoutX .toFloat())
247267 } catch (e: ArrayIndexOutOfBoundsException ) {
248268 // This happens for bidi text on Android 7-8.
249269 // See
@@ -285,6 +305,10 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
285305 // TODO T225199534: Add support for "needsOffscreenAlphaCompositing" to Text
286306 override fun hasOverlappingRendering (): Boolean = false
287307
308+ override fun reactTagForTouch (touchX : Float , touchY : Float ): Int =
309+ getSpanInCoords(touchX.roundToInt(), touchY.roundToInt(), ReactTagSpan ::class .java)?.reactTag
310+ ? : id
311+
288312 @RequiresApi(api = Build .VERSION_CODES .UPSIDE_DOWN_CAKE )
289313 private object Api34Utils {
290314 private var highlightPaths: List <Path >? = null
@@ -331,21 +355,4 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
331355 return spans
332356 }
333357 }
334-
335- override fun reactTagForTouch (touchX : Float , touchY : Float ): Int {
336- val offset = getTextOffsetAt(touchX.roundToInt(), touchY.roundToInt())
337- if (offset < 0 ) {
338- return id
339- }
340-
341- val spanned = text as ? Spanned ? : return id
342- val reactSpans = spanned.getSpans(offset, offset, ReactTagSpan ::class .java)
343- check(reactSpans.size <= 1 )
344-
345- return if (reactSpans.isNotEmpty()) {
346- reactSpans[0 ].reactTag
347- } else {
348- id
349- }
350- }
351358}
0 commit comments