Skip to content

Commit 3e117a1

Browse files
committed
feat: migrate off viewpager to frame layout (WIP)
1 parent 0c20b46 commit 3e117a1

File tree

5 files changed

+44
-111
lines changed

5 files changed

+44
-111
lines changed

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,16 @@ import android.util.Log
1111
import android.util.Size
1212
import android.util.TypedValue
1313
import android.view.Choreographer
14-
import android.view.Gravity
1514
import android.view.HapticFeedbackConstants
1615
import android.view.MenuItem
1716
import android.view.View
1817
import android.view.ViewGroup
1918
import android.widget.FrameLayout
2019
import android.widget.LinearLayout
2120
import android.widget.TextView
21+
import androidx.core.view.forEachIndexed
22+
import androidx.core.view.isGone
23+
import androidx.core.view.isVisible
2224
import androidx.viewpager2.widget.ViewPager2
2325
import coil3.ImageLoader
2426
import coil3.asDrawable
@@ -38,15 +40,15 @@ import com.google.android.material.transition.platform.MaterialFadeThrough
3840
class 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) {

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabViewImpl.kt

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,29 +96,23 @@ class RCTTabViewImpl {
9696
}
9797

9898
fun getChildCount(parent: ReactBottomNavigationView): Int {
99-
return parent.viewPagerAdapter.itemCount ?: 0
99+
return parent.layoutHolder.childCount ?: 0
100100
}
101101

102102
fun getChildAt(parent: ReactBottomNavigationView, index: Int): View? {
103-
return parent.viewPagerAdapter.getChildAt(index)
103+
return parent.layoutHolder.getChildAt(index)
104104
}
105105

106106
fun removeView(parent: ReactBottomNavigationView, view: View) {
107-
parent.viewPagerAdapter.removeChild(view)
107+
parent.layoutHolder.removeView(view)
108108
}
109109

110110
fun removeAllViews(parent: ReactBottomNavigationView) {
111-
parent.viewPagerAdapter.removeAll()
111+
parent.layoutHolder.removeAllViews()
112112
}
113113

114114
fun removeViewAt(parent: ReactBottomNavigationView, index: Int) {
115-
val child = parent.viewPagerAdapter.getChildAt(index)
116-
117-
if (child.parent != null) {
118-
(child.parent as? ViewGroup)?.removeView(child)
119-
}
120-
121-
parent.viewPagerAdapter.removeChildAt(index)
115+
parent.layoutHolder.removeViewAt(index)
122116
}
123117

124118
fun needsCustomLayoutForChildren(): Boolean {

packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/ViewPagerAdapter.kt

Lines changed: 0 additions & 77 deletions
This file was deleted.

packages/react-native-bottom-tabs/android/src/newarch/RCTTabViewManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class RCTTabViewManager(context: ReactApplicationContext) :
138138
}
139139

140140
override fun setDisablePageAnimations(view: ReactBottomNavigationView?, value: Boolean) {
141-
view?.disablePageTransitions = value
141+
view?.disablePageAnimations = value
142142
}
143143

144144
// iOS Methods

packages/react-native-bottom-tabs/android/src/oldarch/RCTTabViewManager.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.rcttabview
22

33
import android.view.View
4-
import android.view.ViewGroup
54
import com.facebook.react.module.annotations.ReactModule
65
import com.facebook.react.uimanager.ThemedReactContext
76
import com.facebook.react.uimanager.annotations.ReactProp
@@ -78,7 +77,6 @@ class RCTTabViewManager(context: ReactApplicationContext) : ViewGroupManager<Rea
7877
tabViewImpl.setSelectedPage(view, key)
7978
}
8079

81-
8280
@ReactProp(name = "labeled")
8381
fun setLabeled(view: ReactBottomNavigationView, flag: Boolean?) {
8482
tabViewImpl.setLabeled(view, flag)
@@ -114,8 +112,12 @@ class RCTTabViewManager(context: ReactApplicationContext) : ViewGroupManager<Rea
114112
tabViewImpl.setActiveIndicatorColor(view, color)
115113
}
116114

117-
// iOS Props
115+
@ReactProp(name = "disablePageAnimations")
116+
fun setDisablePageAnimations(view: ReactBottomNavigationView, flag: Boolean) {
117+
view.disablePageAnimations = flag
118+
}
118119

120+
// iOS Props
119121
@ReactProp(name = "sidebarAdaptable")
120122
fun setSidebarAdaptable(view: ReactBottomNavigationView, flag: Boolean) {
121123
}
@@ -124,10 +126,6 @@ class RCTTabViewManager(context: ReactApplicationContext) : ViewGroupManager<Rea
124126
fun setIgnoresTopSafeArea(view: ReactBottomNavigationView, flag: Boolean) {
125127
}
126128

127-
@ReactProp(name = "disablePageAnimations")
128-
fun setDisablePageAnimations(view: ReactBottomNavigationView, flag: Boolean) {
129-
}
130-
131129
@ReactProp(name = "hapticFeedbackEnabled")
132130
fun setHapticFeedbackEnabled(view: ReactBottomNavigationView, value: Boolean) {
133131
tabViewImpl.setHapticFeedbackEnabled(view, value)

0 commit comments

Comments
 (0)