@@ -14,22 +14,26 @@ import com.facebook.imagepipeline.request.ImageRequestBuilder
1414import com.facebook.react.bridge.Arguments
1515import com.facebook.react.bridge.ReadableArray
1616import com.facebook.react.bridge.WritableMap
17+ import com.facebook.react.modules.core.ReactChoreographer
1718import com.facebook.react.views.imagehelper.ImageSource
1819import com.facebook.react.views.imagehelper.ImageSource.Companion.getTransparentBitmapImageSource
1920import com.google.android.material.bottomnavigation.BottomNavigationView
2021
2122
2223class ReactBottomNavigationView (context : Context ) : BottomNavigationView(context) {
23- private val ANIMATION_DURATION : Long = 300
2424 private val icons: MutableMap <Int , ImageSource > = mutableMapOf ()
25-
25+ private var isLayoutEnqueued = false
2626 var items: MutableList <TabInfo >? = null
2727 var onTabSelectedListener: ((WritableMap ) -> Unit )? = null
2828 private var isAnimating = false
29- private val frameCallback = Choreographer .FrameCallback {
30- if (isAnimating) {
31- measureAndLayout()
32- }
29+
30+ private val layoutCallback = Choreographer .FrameCallback {
31+ isLayoutEnqueued = false
32+ measure(
33+ MeasureSpec .makeMeasureSpec(width, MeasureSpec .EXACTLY ),
34+ MeasureSpec .makeMeasureSpec(height, MeasureSpec .EXACTLY ),
35+ )
36+ layout(left, top, right, bottom)
3337 }
3438
3539 init {
@@ -41,37 +45,33 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
4145
4246 override fun requestLayout () {
4347 super .requestLayout()
44- // Manually trigger measure & layout, as RN on Android skips those.
45- // See this issue: https://github.com/facebook/react-native/issues/17968#issuecomment-721958427
46- this .post {
47- measureAndLayout()
48+ @Suppress(" SENSELESS_COMPARISON" ) // layoutCallback can be null here since this method can be called in init
49+ if (! isLayoutEnqueued && layoutCallback != null ) {
50+ isLayoutEnqueued = true
51+ // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current
52+ // looper loop instead of enqueueing the update in the next loop causing a one frame delay.
53+ ReactChoreographer
54+ .getInstance()
55+ .postFrameCallback(
56+ ReactChoreographer .CallbackType .NATIVE_ANIMATED_MODULE ,
57+ layoutCallback,
58+ )
4859 }
4960 }
5061
5162 private fun onTabSelected (item : MenuItem ) {
63+ if (isLayoutEnqueued) {
64+ return ;
65+ }
5266 val selectedItem = items?.first { it.title == item.title }
5367 selectedItem?.let {
5468 val event = Arguments .createMap().apply {
5569 putString(" key" , selectedItem.key)
5670 }
5771 onTabSelectedListener?.invoke(event)
58- startAnimation()
5972 }
6073 }
6174
62- // Refresh TabView children to fix issue with animations.
63- // https://github.com/facebook/react-native/issues/17968#issuecomment-697136929
64- private fun startAnimation () {
65- if (labelVisibilityMode != LABEL_VISIBILITY_AUTO ) {
66- return
67- }
68- isAnimating = true
69- Choreographer .getInstance().postFrameCallback(frameCallback)
70- postDelayed({
71- isAnimating = false
72- }, ANIMATION_DURATION )
73- }
74-
7575 fun updateItems (items : MutableList <TabInfo >) {
7676 this .items = items
7777 items.forEachIndexed {index, item ->
@@ -140,14 +140,6 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
140140 return BitmapDrawable (resources, bitmap)
141141 }
142142
143- // Fixes issues with BottomNavigationView children layouting.
144- private fun measureAndLayout () {
145- measure(
146- MeasureSpec .makeMeasureSpec(width, MeasureSpec .EXACTLY ),
147- MeasureSpec .makeMeasureSpec(height, MeasureSpec .EXACTLY ))
148- layout(left, top, right, bottom)
149- }
150-
151143 override fun onDetachedFromWindow () {
152144 super .onDetachedFromWindow()
153145 isAnimating = false
0 commit comments