mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Migrate OpacityAnimation to Kotlin (#49822)
Summary: Migrate com.facebook.react.uimanager.layoutanimation.OpacityAnimation to Kotlin. I moved out `OpacityAnimationListener` as well in this PR to separate concerns and align more with the codebase. ## Changelog: [INTERNAL] - Migrate com.facebook.react.uimanager.layoutanimation.OpacityAnimation to Kotlin Pull Request resolved: https://github.com/facebook/react-native/pull/49822 Test Plan: ```bash yarn test-android yarn android ``` Reviewed By: javache Differential Revision: D70588884 Pulled By: alanleedev fbshipit-source-id: 1b70f5e5facd5f47b828090e7c63a218229000cb
This commit is contained in:
committed by
Facebook GitHub Bot
parent
bf7a0e5798
commit
ff84ae9188
-81
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.Transformation;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel;
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger;
|
||||
|
||||
/**
|
||||
* Animation responsible for updating opacity of a view. It should ideally use hardware texture to
|
||||
* optimize rendering performances.
|
||||
*/
|
||||
/* package */ @Nullsafe(Nullsafe.Mode.LOCAL)
|
||||
@LegacyArchitecture
|
||||
class OpacityAnimation extends Animation {
|
||||
|
||||
static {
|
||||
LegacyArchitectureLogger.assertWhenLegacyArchitectureMinifyingEnabled(
|
||||
"OpacityAnimation", LegacyArchitectureLogLevel.WARNING);
|
||||
}
|
||||
|
||||
static class OpacityAnimationListener implements Animation.AnimationListener {
|
||||
|
||||
private final View mView;
|
||||
private boolean mLayerTypeChanged = false;
|
||||
|
||||
public OpacityAnimationListener(View view) {
|
||||
mView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
if (mView.hasOverlappingRendering() && mView.getLayerType() == View.LAYER_TYPE_NONE) {
|
||||
mLayerTypeChanged = true;
|
||||
mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
if (mLayerTypeChanged) {
|
||||
mView.setLayerType(View.LAYER_TYPE_NONE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private final View mView;
|
||||
private final float mStartOpacity, mDeltaOpacity;
|
||||
|
||||
public OpacityAnimation(View view, float startOpacity, float endOpacity) {
|
||||
mView = view;
|
||||
mStartOpacity = startOpacity;
|
||||
mDeltaOpacity = endOpacity - startOpacity;
|
||||
|
||||
setAnimationListener(new OpacityAnimationListener(view));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTransformation(float interpolatedTime, Transformation t) {
|
||||
mView.setAlpha(mStartOpacity + mDeltaOpacity * interpolatedTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willChangeBounds() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.Transformation
|
||||
import com.facebook.react.common.annotations.VisibleForTesting
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitecture
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogLevel
|
||||
import com.facebook.react.common.annotations.internal.LegacyArchitectureLogger
|
||||
|
||||
/**
|
||||
* Animation responsible for updating opacity of a view. It should ideally use hardware texture to
|
||||
* optimize rendering performances.
|
||||
*/
|
||||
@LegacyArchitecture
|
||||
internal class OpacityAnimation(
|
||||
private val view: View,
|
||||
private val startOpacity: Float,
|
||||
endOpacity: Float
|
||||
) : Animation() {
|
||||
private val deltaOpacity = endOpacity - startOpacity
|
||||
|
||||
init {
|
||||
setAnimationListener(OpacityAnimationListener(view))
|
||||
LegacyArchitectureLogger.assertWhenLegacyArchitectureMinifyingEnabled(
|
||||
"OpacityAnimation", LegacyArchitectureLogLevel.WARNING)
|
||||
}
|
||||
|
||||
class OpacityAnimationListener(private val view: View) : Animation.AnimationListener {
|
||||
private var layerTypeChanged = false
|
||||
|
||||
override fun onAnimationStart(animation: Animation) {
|
||||
if (view.hasOverlappingRendering() && view.layerType == View.LAYER_TYPE_NONE) {
|
||||
layerTypeChanged = true
|
||||
view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
if (layerTypeChanged) {
|
||||
view.setLayerType(View.LAYER_TYPE_NONE, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnimationRepeat(animation: Animation) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
|
||||
view.alpha = startOpacity + deltaOpacity * interpolatedTime
|
||||
}
|
||||
|
||||
override fun willChangeBounds(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.react.uimanager.layoutanimation
|
||||
|
||||
import android.view.View
|
||||
import android.view.animation.Transformation
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.mockito.kotlin.never
|
||||
import org.mockito.kotlin.verify
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class OpacityAnimationTest {
|
||||
private lateinit var view: View
|
||||
private lateinit var animation: OpacityAnimation
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
view = View(RuntimeEnvironment.getApplication())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun applyTransformation_updatesAlphaCorrectly() {
|
||||
val startOpacity = 0.5f
|
||||
val endOpacity = 1.0f
|
||||
animation = OpacityAnimation(view, startOpacity, endOpacity)
|
||||
|
||||
val transformation = Transformation()
|
||||
animation.applyTransformation(0.0f, transformation)
|
||||
assertThat(view.alpha).isEqualTo(0.5f)
|
||||
|
||||
animation.applyTransformation(0.5f, transformation)
|
||||
assertThat(view.alpha).isEqualTo(0.75f)
|
||||
|
||||
animation.applyTransformation(1.0f, transformation)
|
||||
assertThat(view.alpha).isEqualTo(1.0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun willChangeBounds_returnsFalse() {
|
||||
animation = OpacityAnimation(view, 0.5f, 1.0f)
|
||||
assertThat(animation.willChangeBounds()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onAnimationStart_setsLayerTypeHardwareIfOverlappingRendering() {
|
||||
val spyView: View = mock {
|
||||
on { hasOverlappingRendering() } doReturn true
|
||||
on { layerType } doReturn View.LAYER_TYPE_NONE
|
||||
}
|
||||
|
||||
val listener = OpacityAnimation.OpacityAnimationListener(spyView)
|
||||
listener.onAnimationStart(mock())
|
||||
|
||||
verify(spyView).setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onAnimationStart_doesNotChangeLayerTypeIfAlreadySet() {
|
||||
val spyView: View = mock {
|
||||
on { hasOverlappingRendering() } doReturn true
|
||||
on { layerType } doReturn View.LAYER_TYPE_HARDWARE
|
||||
}
|
||||
|
||||
val listener = OpacityAnimation.OpacityAnimationListener(spyView)
|
||||
listener.onAnimationStart(mock())
|
||||
|
||||
verify(spyView, never()).setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onAnimationEnd_resetsLayerTypeIfChanged() {
|
||||
val spyView: View = mock {
|
||||
on { hasOverlappingRendering() } doReturn true
|
||||
on { layerType } doReturn View.LAYER_TYPE_NONE
|
||||
}
|
||||
|
||||
val listener = OpacityAnimation.OpacityAnimationListener(spyView)
|
||||
listener.onAnimationStart(mock())
|
||||
listener.onAnimationEnd(mock())
|
||||
|
||||
verify(spyView).setLayerType(View.LAYER_TYPE_NONE, null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onAnimationRepeat_doesNothing() {
|
||||
val listener = OpacityAnimation.OpacityAnimationListener(mock())
|
||||
listener.onAnimationRepeat(mock())
|
||||
|
||||
// No assertions needed, just verifying it runs without errors
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user