@@ -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,35 +45,31 @@ 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()
59- }
60- }
61-
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
6772 }
68- isAnimating = true
69- Choreographer .getInstance().postFrameCallback(frameCallback)
70- postDelayed({
71- isAnimating = false
72- }, ANIMATION_DURATION )
7373 }
7474
7575 fun updateItems (items : MutableList <TabInfo >) {
@@ -117,6 +117,16 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
117117 }
118118 }
119119
120+ fun setConfig (config : TabViewConfig ) {
121+ labelVisibilityMode = if (config.labeled == false ) {
122+ LABEL_VISIBILITY_UNLABELED
123+ } else if (config.labeled == true ) {
124+ LABEL_VISIBILITY_LABELED
125+ } else {
126+ LABEL_VISIBILITY_AUTO
127+ }
128+ }
129+
120130 private fun getDrawable (imageSource : ImageSource ): Drawable {
121131 // TODO: Check if this can be done using some built-in React Native class
122132 val imageRequest = ImageRequestBuilder .newBuilderWithSource(imageSource.uri).build()
@@ -130,14 +140,6 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
130140 return BitmapDrawable (resources, bitmap)
131141 }
132142
133- // Fixes issues with BottomNavigationView children layouting.
134- private fun measureAndLayout () {
135- measure(
136- MeasureSpec .makeMeasureSpec(width, MeasureSpec .EXACTLY ),
137- MeasureSpec .makeMeasureSpec(height, MeasureSpec .EXACTLY ))
138- layout(left, top, right, bottom)
139- }
140-
141143 override fun onDetachedFromWindow () {
142144 super .onDetachedFromWindow()
143145 isAnimating = false
0 commit comments