@@ -190,25 +190,56 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
190
190
* @return a clickable span that's located where the click occurred, or: `null` if no clickable
191
191
* span was located there
192
192
*/
193
- private fun getClickableSpanInCoords (x : Int , y : Int ): ClickableSpan ? {
193
+ private fun getClickableSpanInCoords (x : Int , y : Int ): ClickableSpan ? =
194
+ getSpanInCoords(x, y, ClickableSpan ::class .java)
195
+
196
+ private fun <T > getSpanInCoords (x : Int , y : Int , clazz : Class <T >): T ? {
194
197
val offset = getTextOffsetAt(x, y)
195
198
if (offset < 0 ) {
196
199
return null
197
200
}
198
201
199
202
val spanned = text as ? Spanned ? : return null
200
203
201
- val clickableSpans = spanned.getSpans(offset, offset, ClickableSpan ::class .java)
202
- if (clickableSpans.isNotEmpty()) {
203
- return clickableSpans[0 ]
204
+ val spans = spanned.getSpans(offset, offset, clazz)
205
+ if (spans.isEmpty()) {
206
+ return null
207
+ }
208
+
209
+ // When we have multiple spans marked with SPAN_EXCLUSIVE_INCLUSIVE next to each other, both
210
+ // spans are returned by getSpans
211
+ check(spans.size <= 2 )
212
+ for (span in spans) {
213
+ val spanFlags = spanned.getSpanFlags(span)
214
+ val inclusiveStart =
215
+ if ((spanFlags and Spanned .SPAN_INCLUSIVE_INCLUSIVE ) != 0 ||
216
+ (spanFlags and Spanned .SPAN_INCLUSIVE_EXCLUSIVE ) != 0 ) {
217
+ spanned.getSpanStart(span)
218
+ } else {
219
+ spanned.getSpanStart(span) + 1
220
+ }
221
+ val inclusiveEnd =
222
+ if ((spanFlags and Spanned .SPAN_INCLUSIVE_INCLUSIVE ) != 0 ||
223
+ (spanFlags and Spanned .SPAN_EXCLUSIVE_INCLUSIVE ) != 0 ) {
224
+ spanned.getSpanEnd(span)
225
+ } else {
226
+ spanned.getSpanEnd(span) - 1
227
+ }
228
+
229
+ if (offset >= inclusiveStart && offset <= inclusiveEnd) {
230
+ return span
231
+ }
204
232
}
205
233
206
234
return null
207
235
}
208
236
209
237
private fun getTextOffsetAt (x : Int , y : Int ): Int {
238
+ val layoutX = x - paddingLeft
239
+ val layoutY = y - (paddingTop + (preparedLayout?.verticalOffset?.roundToInt() ? : 0 ))
240
+
210
241
val layout = preparedLayout?.layout ? : return - 1
211
- val line = layout.getLineForVertical(y )
242
+ val line = layout.getLineForVertical(layoutY )
212
243
213
244
val left: Float
214
245
val right: Float
@@ -238,12 +269,12 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
238
269
right = if (rtl) layout.getParagraphRight(line).toFloat() else layout.getLineMax(line)
239
270
}
240
271
241
- if (x < left || x > right) {
272
+ if (layoutX < left || layoutX > right) {
242
273
return - 1
243
274
}
244
275
245
276
return try {
246
- layout.getOffsetForHorizontal(line, x .toFloat())
277
+ layout.getOffsetForHorizontal(line, layoutX .toFloat())
247
278
} catch (e: ArrayIndexOutOfBoundsException ) {
248
279
// This happens for bidi text on Android 7-8.
249
280
// See
@@ -332,20 +363,6 @@ internal class PreparedLayoutTextView(context: Context) : ViewGroup(context), Re
332
363
}
333
364
}
334
365
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
- }
366
+ override fun reactTagForTouch (touchX : Float , touchY : Float ): Int =
367
+ getSpanInCoords(x.roundToInt(), y.roundToInt(), ReactTagSpan ::class .java)?.reactTag ? : id
351
368
}
0 commit comments