Skip to content

Commit

Permalink
Merge pull request #6 from okwasniewski/fix/android-animations
Browse files Browse the repository at this point in the history
fix: Android animations
  • Loading branch information
okwasniewski authored Oct 2, 2024
2 parents a3c1f65 + c3dbd31 commit c0990a9
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 28 deletions.
67 changes: 41 additions & 26 deletions android/src/main/java/com/rcttabview/RCTTabView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,65 @@ import android.content.Context
import android.view.Choreographer
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.content.res.AppCompatResources
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.google.android.material.bottomnavigation.BottomNavigationView

class ReactBottomNavigationView(context: Context) : BottomNavigationView(context) {
private var onTabSelectedListener: ((WritableMap) -> Unit)? = null
private val ANIMATION_DURATION: Long = 300

var items: MutableList<TabInfo>? = null
var onTabSelectedListener: ((WritableMap) -> Unit)? = null
private var isAnimating = false
private val frameCallback = Choreographer.FrameCallback {
if (isAnimating) {
measureAndLayout()
}
}

init {
// TODO: Refactor this outside of TabView (attach listener in ViewManager).
setOnItemSelectedListener { item ->
onTabSelected(item)
true
}
}

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
refreshViewChildrenLayout(this)
override fun requestLayout() {
super.requestLayout()
// Manually trigger measure & layout, as RN on Android skips those.
// See this issue: https://github.com/facebook/react-native/issues/17968#issuecomment-721958427
this.post {
measureAndLayout()
}
}

private fun onTabSelected(item: MenuItem) {
val selectedItem = items?.first { it.title == item.title }
if (selectedItem == null) {
return
}

startAnimation()

val event = Arguments.createMap().apply {
putString("key", selectedItem.key)
}
onTabSelectedListener?.invoke(event)

// Refresh TabView children to fix issue with animations.
// https://github.com/facebook/react-native/issues/17968#issuecomment-697136929
Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
refreshViewChildrenLayout(this@ReactBottomNavigationView)
Choreographer.getInstance().postFrameCallback(this)
}
})
}

fun setOnTabSelectedListener(listener: (WritableMap) -> Unit) {
onTabSelectedListener = listener
// Refresh TabView children to fix issue with animations.
// https://github.com/facebook/react-native/issues/17968#issuecomment-697136929
private fun startAnimation() {
if (labelVisibilityMode != LABEL_VISIBILITY_AUTO) {
return
}
isAnimating = true
Choreographer.getInstance().postFrameCallback(frameCallback)
postDelayed({
isAnimating = false
}, ANIMATION_DURATION)
}

fun updateItems(items: MutableList<TabInfo>) {
Expand All @@ -73,18 +87,19 @@ class ReactBottomNavigationView(context: Context) : BottomNavigationView(context
removeBadge(index)
}
}

refreshViewChildrenLayout(this)
}


// Fixes issues with BottomNavigationView children layouting.
private fun refreshViewChildrenLayout(view: View) {
view.post {
view.measure(
View.MeasureSpec.makeMeasureSpec(view.width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(view.height, View.MeasureSpec.EXACTLY))
view.layout(view.left, view.top, view.right, view.bottom)
}
private fun measureAndLayout() {
viewTreeObserver.dispatchOnGlobalLayout();
measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY))
layout(left, top, right, bottom)
}

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
isAnimating = false
}
}
4 changes: 2 additions & 2 deletions android/src/main/java/com/rcttabview/RCTTabViewViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ class RCTTabViewViewManager :
public override fun createViewInstance(context: ThemedReactContext): ReactBottomNavigationView {
eventDispatcher = context.getNativeModule(UIManagerModule::class.java)!!.eventDispatcher
val view = ReactBottomNavigationView(context)
view.setOnTabSelectedListener { data ->
data.getString("key")?.let {
view.onTabSelectedListener = { data ->
data.getString("key")?.let {
eventDispatcher.dispatchEvent(PageSelectedEvent(viewTag = view.id, key = it ))
}
}
Expand Down

0 comments on commit c0990a9

Please sign in to comment.