@@ -4,51 +4,65 @@ import android.content.Context
44import android.view.Choreographer
55import android.view.MenuItem
66import android.view.View
7+ import android.view.ViewGroup
78import androidx.appcompat.content.res.AppCompatResources
89import com.facebook.react.bridge.Arguments
910import com.facebook.react.bridge.WritableMap
1011import com.google.android.material.bottomnavigation.BottomNavigationView
1112
1213class ReactBottomNavigationView (context : Context ) : BottomNavigationView(context) {
13- private var onTabSelectedListener: ((WritableMap ) -> Unit )? = null
14+ private val ANIMATION_DURATION : Long = 300
15+
1416 var items: MutableList <TabInfo >? = null
17+ var onTabSelectedListener: ((WritableMap ) -> Unit )? = null
18+ private var isAnimating = false
19+ private val frameCallback = Choreographer .FrameCallback {
20+ if (isAnimating) {
21+ measureAndLayout()
22+ }
23+ }
1524
1625 init {
17- // TODO: Refactor this outside of TabView (attach listener in ViewManager).
1826 setOnItemSelectedListener { item ->
1927 onTabSelected(item)
2028 true
2129 }
2230 }
2331
24- override fun onLayout (changed : Boolean , left : Int , top : Int , right : Int , bottom : Int ) {
25- super .onLayout(changed, left, top, right, bottom)
26- refreshViewChildrenLayout(this )
32+ override fun requestLayout () {
33+ super .requestLayout()
34+ // Manually trigger measure & layout, as RN on Android skips those.
35+ // See this issue: https://github.com/facebook/react-native/issues/17968#issuecomment-721958427
36+ this .post {
37+ measureAndLayout()
38+ }
2739 }
2840
2941 private fun onTabSelected (item : MenuItem ) {
3042 val selectedItem = items?.first { it.title == item.title }
3143 if (selectedItem == null ) {
3244 return
3345 }
46+
47+ startAnimation()
3448
3549 val event = Arguments .createMap().apply {
3650 putString(" key" , selectedItem.key)
3751 }
3852 onTabSelectedListener?.invoke(event)
39-
40- // Refresh TabView children to fix issue with animations.
41- // https://github.com/facebook/react-native/issues/17968#issuecomment-697136929
42- Choreographer .getInstance().postFrameCallback(object : Choreographer .FrameCallback {
43- override fun doFrame (frameTimeNanos : Long ) {
44- refreshViewChildrenLayout(this @ReactBottomNavigationView)
45- Choreographer .getInstance().postFrameCallback(this )
46- }
47- })
4853 }
4954
50- fun setOnTabSelectedListener (listener : (WritableMap ) -> Unit ) {
51- onTabSelectedListener = listener
55+ // Refresh TabView children to fix issue with animations.
56+ // https://github.com/facebook/react-native/issues/17968#issuecomment-697136929
57+ private fun startAnimation () {
58+ if (labelVisibilityMode != LABEL_VISIBILITY_AUTO ) {
59+ return
60+ }
61+ isAnimating = true
62+ Choreographer .getInstance().postFrameCallback(frameCallback)
63+ postDelayed({
64+ isAnimating = false
65+ }, ANIMATION_DURATION )
5266 }
5367
5468 fun updateItems (items : MutableList <TabInfo >) {
@@ -73,18 +87,19 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
7387 removeBadge(index)
7488 }
7589 }
76-
77- refreshViewChildrenLayout(this )
7890 }
7991
80-
8192 // Fixes issues with BottomNavigationView children layouting.
82- private fun refreshViewChildrenLayout (view : View ) {
83- view.post {
84- view.measure(
85- View .MeasureSpec .makeMeasureSpec(view.width, View .MeasureSpec .EXACTLY ),
86- View .MeasureSpec .makeMeasureSpec(view.height, View .MeasureSpec .EXACTLY ))
87- view.layout(view.left, view.top, view.right, view.bottom)
88- }
93+ private fun measureAndLayout () {
94+ viewTreeObserver.dispatchOnGlobalLayout();
95+ measure(
96+ MeasureSpec .makeMeasureSpec(width, MeasureSpec .EXACTLY ),
97+ MeasureSpec .makeMeasureSpec(height, MeasureSpec .EXACTLY ))
98+ layout(left, top, right, bottom)
99+ }
100+
101+ override fun onDetachedFromWindow () {
102+ super .onDetachedFromWindow()
103+ isAnimating = false
89104 }
90105}
0 commit comments