@@ -11,14 +11,16 @@ import android.util.Log
1111import android.util.Size
1212import android.util.TypedValue
1313import android.view.Choreographer
14- import android.view.Gravity
1514import android.view.HapticFeedbackConstants
1615import android.view.MenuItem
1716import android.view.View
1817import android.view.ViewGroup
1918import android.widget.FrameLayout
2019import android.widget.LinearLayout
2120import android.widget.TextView
21+ import androidx.core.view.forEachIndexed
22+ import androidx.core.view.isGone
23+ import androidx.core.view.isVisible
2224import androidx.viewpager2.widget.ViewPager2
2325import coil3.ImageLoader
2426import coil3.asDrawable
@@ -38,15 +40,15 @@ import com.google.android.material.transition.platform.MaterialFadeThrough
3840class ReactBottomNavigationView (context : ReactContext ) : LinearLayout(context) {
3941 private val reactContext: ReactContext = context
4042 private val bottomNavigation = BottomNavigationView (context)
41- private val viewPager = ViewPager2 (context)
42- val viewPagerAdapter = ViewPagerAdapter ()
43+ val layoutHolder = FrameLayout (context)
4344
4445 var onTabSelectedListener: ((key: String ) -> Unit )? = null
4546 var onTabLongPressedListener: ((key: String ) -> Unit )? = null
4647 var onNativeLayoutListener: ((width: Double , height: Double ) -> Unit )? = null
47- var disablePageTransitions = false
48+ var disablePageAnimations = false
4849 var items: MutableList <TabInfo > = mutableListOf ()
4950
51+ private var isLayoutEnqueued = false
5052 private var selectedItem: String? = null
5153 private val iconSources: MutableMap <Int , ImageSource > = mutableMapOf ()
5254 private var activeTintColor: Int? = null
@@ -67,11 +69,9 @@ class ReactBottomNavigationView(context: ReactContext) : LinearLayout(context) {
6769
6870 init {
6971 orientation = VERTICAL
70- viewPager.adapter = viewPagerAdapter
71- viewPager.isUserInputEnabled = false
7272
7373 addView(
74- viewPager , LayoutParams (
74+ layoutHolder , LayoutParams (
7575 LayoutParams .MATCH_PARENT ,
7676 0 ,
7777 ).apply { weight = 1f }
@@ -89,8 +89,8 @@ class ReactBottomNavigationView(context: ReactContext) : LinearLayout(context) {
8989 val newHeight = bottom - top
9090
9191 if (newWidth != lastReportedSize?.width || newHeight != lastReportedSize?.height) {
92- val dpWidth = Utils .convertPixelsToDp(context, viewPager .width)
93- val dpHeight = Utils .convertPixelsToDp(context, viewPager .height)
92+ val dpWidth = Utils .convertPixelsToDp(context, layoutHolder .width)
93+ val dpHeight = Utils .convertPixelsToDp(context, layoutHolder .height)
9494
9595 onNativeLayoutListener?.invoke(dpWidth, dpHeight)
9696 lastReportedSize = Size (newWidth, newHeight)
@@ -105,18 +105,27 @@ class ReactBottomNavigationView(context: ReactContext) : LinearLayout(context) {
105105 }
106106
107107 override fun addView (child : View , index : Int , params : ViewGroup .LayoutParams ? ) {
108- if (child == = viewPager || child == = bottomNavigation) {
108+ if (child == = layoutHolder || child == = bottomNavigation) {
109109 super .addView(child, index, params)
110110 } else {
111- viewPagerAdapter.addChild(child, index)
111+ layoutHolder.addView(child, index)
112+ child.visibility = INVISIBLE
113+ child.isEnabled = false
114+
112115 val itemKey = items[index].key
113116 if (selectedItem == itemKey) {
114117 setSelectedIndex(index)
118+ refreshLayout()
115119 }
116120 }
117121 }
118122
119123 private val layoutCallback = Choreographer .FrameCallback {
124+ isLayoutEnqueued = false
125+ refreshLayout()
126+ }
127+
128+ private fun refreshLayout () {
120129 measure(
121130 MeasureSpec .makeMeasureSpec(width, MeasureSpec .EXACTLY ),
122131 MeasureSpec .makeMeasureSpec(height, MeasureSpec .EXACTLY ),
@@ -128,7 +137,8 @@ class ReactBottomNavigationView(context: ReactContext) : LinearLayout(context) {
128137 super .requestLayout()
129138 @Suppress(" SENSELESS_COMPARISON" ) // layoutCallback can be null here since this method can be called in init
130139
131- if (layoutCallback != null ) {
140+ if (! isLayoutEnqueued && layoutCallback != null ) {
141+ isLayoutEnqueued = true
132142 // we use NATIVE_ANIMATED_MODULE choreographer queue because it allows us to catch the current
133143 // looper loop instead of enqueueing the update in the next loop causing a one frame delay.
134144 ReactChoreographer
@@ -142,11 +152,19 @@ class ReactBottomNavigationView(context: ReactContext) : LinearLayout(context) {
142152
143153 private fun setSelectedIndex (itemId : Int ) {
144154 bottomNavigation.selectedItemId = itemId
145- if (! disablePageTransitions ) {
155+ if (! disablePageAnimations ) {
146156 val fadeThrough = MaterialFadeThrough ()
147- TransitionManager .beginDelayedTransition(this , fadeThrough)
157+ TransitionManager .beginDelayedTransition(layoutHolder, fadeThrough)
158+ }
159+ layoutHolder.forEachIndexed { index, view ->
160+ if (itemId == index) {
161+ view.visibility = VISIBLE
162+ view.isEnabled = true
163+ } else {
164+ view.visibility = INVISIBLE
165+ view.isEnabled = false
166+ }
148167 }
149- viewPager.setCurrentItem(itemId, false )
150168 }
151169
152170 private fun onTabSelected (item : MenuItem ) {
0 commit comments