Skip to content

Commit 29b0ca7

Browse files
committed
fix: android layout calculation race condition
1 parent 04a751a commit 29b0ca7

4 files changed

Lines changed: 58 additions & 21 deletions

File tree

android/src/main/java/com/swmansion/reactnativerichtexteditor/ReactNativeRichTextEditorView.kt

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.graphics.BlendModeColorFilter
66
import android.graphics.Color
77
import android.graphics.Rect
88
import android.os.Build
9+
import android.text.Editable
910
import android.text.Spannable
1011
import android.text.StaticLayout
1112
import android.util.AttributeSet
@@ -53,6 +54,8 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
5354
val mentionHandler: MentionHandler? = MentionHandler(this)
5455
var richTextStyle: RichTextStyle = RichTextStyle(this, null)
5556
var spanWatcher: EditorSpanWatcher? = null
57+
var layoutManager: ReactNativeRichTextEditorViewLayoutManager? =
58+
ReactNativeRichTextEditorViewLayoutManager(this)
5659

5760
var fontSize: Float? = null
5861
private var autoFocus = false
@@ -66,6 +69,9 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
6669

6770
private var inputMethodManager: InputMethodManager? = null
6871

72+
private var maxWidth: Float = 0f
73+
private var currentMeasureSize: Pair<Float, Float> = Pair(0f, 0f)
74+
6975
constructor(context: Context) : super(context) {
7076
prepareComponent()
7177
}
@@ -274,25 +280,6 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
274280
return false
275281
}
276282

277-
fun measureSize(maxWidth: Float): Pair<Float, Float> {
278-
Log.d("ReactNativeRichTextEditorView", "measureSize called with maxWidth: $maxWidth")
279-
280-
val paint = this.paint
281-
val spannable = text as Spannable
282-
val spannableLength = spannable.length
283-
284-
val staticLayout = StaticLayout.Builder
285-
.obtain(spannable, 0, spannableLength, paint, maxWidth.toInt())
286-
.setIncludePad(true)
287-
.setLineSpacing(0f, 1f)
288-
.build()
289-
290-
val heightInSP = PixelUtil.toDIPFromPixel(staticLayout.height.toFloat())
291-
val widthInSP = PixelUtil.toDIPFromPixel(maxWidth)
292-
293-
return Pair(widthInSP, heightInSP)
294-
}
295-
296283
fun updateTypeface() {
297284
if (!typefaceDirty) return
298285
typefaceDirty = false
@@ -386,10 +373,11 @@ class ReactNativeRichTextEditorView : AppCompatEditText {
386373

387374
// Update shadow node's state in order to recalculate layout
388375
fun updateYogaState() {
376+
layoutManager?.measureSize(text ?: "")
377+
389378
val counter = forceHeightRecalculationCounter
390379
forceHeightRecalculationCounter++
391380
val state = Arguments.createMap()
392-
393381
state.putInt("forceHeightRecalculationCounter", counter)
394382
stateWrapper?.updateState(state)
395383
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.swmansion.reactnativerichtexteditor
2+
3+
import android.text.StaticLayout
4+
import com.facebook.react.uimanager.PixelUtil
5+
6+
class ReactNativeRichTextEditorViewLayoutManager(private val editorView: ReactNativeRichTextEditorView) {
7+
private var cachedSize: Pair<Float, Float> = Pair(0f, 0f)
8+
private var cachedYogaWidth: Float = 0f
9+
10+
fun getMeasuredSize(maxWidth: Float): Pair<Float, Float> {
11+
if (maxWidth == cachedYogaWidth) {
12+
return cachedSize
13+
}
14+
15+
val text = editorView.text ?: ""
16+
val result = measureAndCacheSize(text, maxWidth)
17+
cachedYogaWidth = maxWidth
18+
return result
19+
}
20+
21+
fun measureSize(text: CharSequence): Pair<Float, Float> {
22+
return measureAndCacheSize(text, cachedYogaWidth)
23+
}
24+
25+
private fun measureAndCacheSize(text: CharSequence, maxWidth: Float): Pair<Float, Float> {
26+
val result = measureSize(text, maxWidth)
27+
cachedSize = result
28+
return result
29+
}
30+
31+
private fun measureSize(text: CharSequence, maxWidth: Float): Pair<Float, Float> {
32+
val paint = editorView.paint
33+
val textLength = text.length
34+
35+
val staticLayout = StaticLayout.Builder
36+
.obtain(text, 0, textLength, paint, maxWidth.toInt())
37+
.setIncludePad(true)
38+
.setLineSpacing(0f, 1f)
39+
.build()
40+
41+
val heightInSP = PixelUtil.toDIPFromPixel(staticLayout.height.toFloat())
42+
val widthInSP = PixelUtil.toDIPFromPixel(maxWidth)
43+
44+
return Pair(widthInSP, heightInSP)
45+
}
46+
}

android/src/main/java/com/swmansion/reactnativerichtexteditor/ReactNativeRichTextEditorViewManager.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.swmansion.reactnativerichtexteditor
22

33
import android.content.Context
4+
import android.text.Spannable
45
import com.facebook.react.bridge.ReadableArray
56
import com.facebook.react.bridge.ReadableMap
67
import com.facebook.react.module.annotations.ReactModule
@@ -262,7 +263,7 @@ class ReactNativeRichTextEditorViewManager : SimpleViewManager<ReactNativeRichTe
262263
heightMode: YogaMeasureMode?,
263264
attachmentsPositions: FloatArray?
264265
): Long {
265-
val size = this.view?.measureSize(width)
266+
val size = this.view?.layoutManager?.getMeasuredSize(width)
266267

267268
if (size != null) {
268269
return YogaMeasureOutput.make(size.first, size.second)

android/src/main/java/com/swmansion/reactnativerichtexteditor/watchers/EditorTextWatcher.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ class EditorTextWatcher(private val editorView: ReactNativeRichTextEditorView) :
1717

1818
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
1919
endCursorPosition = start + count
20+
21+
editorView.layoutManager?.measureSize(s ?: "")
2022
}
2123

2224
override fun afterTextChanged(s: Editable?) {

0 commit comments

Comments
 (0)