Skip to content

Commit

Permalink
Disable by default layout animation (software-mansion#2651)
Browse files Browse the repository at this point in the history
## Description

I added the possibility to disable layout animations. By default layout animations are disabled, but you don't need to turn it on manually, because these will be enabled if you will use the `entering`/`exiting`/`layout` prop in an animated component. You also have the possibility to disable/enable it manually.

## Example
```js
import { enableLayoutAnimations } from 'react-native-reanimated';

enableLayoutAnimations(false);
```
  • Loading branch information
piaskowyk authored Nov 23, 2021
1 parent 721751e commit 5a1407c
Show file tree
Hide file tree
Showing 21 changed files with 193 additions and 10 deletions.
8 changes: 8 additions & 0 deletions Common/cpp/NativeModules/NativeReanimatedModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <memory>
#include <thread>
#include "EventHandlerRegistry.h"
#include "FeaturesConfig.h"
#include "FrozenObject.h"
#include "JSIStoreValueUser.h"
#include "Mapper.h"
Expand Down Expand Up @@ -234,6 +235,13 @@ jsi::Value NativeReanimatedModule::getViewProp(
return jsi::Value::undefined();
}

jsi::Value NativeReanimatedModule::enableLayoutAnimations(
jsi::Runtime &rt,
const jsi::Value &config) {
FeaturesConfig::setLayoutAnimationEnabled(config.getBool());
return jsi::Value::undefined();
}

void NativeReanimatedModule::onEvent(
std::string eventName,
std::string eventAsString) {
Expand Down
13 changes: 13 additions & 0 deletions Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ static jsi::Value __hostFunction_NativeReanimatedModuleSpec_getViewProp(
return jsi::Value::undefined();
}

static jsi::Value
__hostFunction_NativeReanimatedModuleSpec_enableLayoutAnimations(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->enableLayoutAnimations(rt, std::move(args[0]));
return jsi::Value::undefined();
}

NativeReanimatedModuleSpec::NativeReanimatedModuleSpec(
std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("NativeReanimated", jsInvoker) {
Expand All @@ -124,6 +135,8 @@ NativeReanimatedModuleSpec::NativeReanimatedModuleSpec(

methodMap_["getViewProp"] =
MethodMetadata{3, __hostFunction_NativeReanimatedModuleSpec_getViewProp};
methodMap_["enableLayoutAnimations"] = MethodMetadata{
2, __hostFunction_NativeReanimatedModuleSpec_enableLayoutAnimations};
}

} // namespace reanimated
5 changes: 5 additions & 0 deletions Common/cpp/Tools/FeaturesConfig.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#include "FeaturesConfig.h"

namespace reanimated {
bool FeaturesConfig::_isLayoutAnimationEnabled = false;
}
3 changes: 3 additions & 0 deletions Common/cpp/headers/NativeModules/NativeReanimatedModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ class NativeReanimatedModule : public NativeReanimatedModuleSpec,
const jsi::Value &propName,
const jsi::Value &callback) override;

jsi::Value enableLayoutAnimations(jsi::Runtime &rt, const jsi::Value &config)
override;

void onRender(double timestampMs);
void onEvent(std::string eventName, std::string eventAsString);
bool isAnyHandlerWaitingForEvent(std::string eventName);
Expand Down
5 changes: 5 additions & 0 deletions Common/cpp/headers/NativeModules/NativeReanimatedModuleSpec.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class JSI_EXPORT NativeReanimatedModuleSpec : public TurboModule {
const jsi::Value &viewTag,
const jsi::Value &propName,
const jsi::Value &callback) = 0;

// other
virtual jsi::Value enableLayoutAnimations(
jsi::Runtime &rt,
const jsi::Value &config) = 0;
};

} // namespace reanimated
19 changes: 19 additions & 0 deletions Common/cpp/headers/Tools/FeaturesConfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once
#include <string>

namespace reanimated {

class FeaturesConfig {
public:
static inline bool isLayoutAnimationEnabled() {
return _isLayoutAnimationEnabled;
}
static inline void setLayoutAnimationEnabled(bool isLayoutAnimationEnabled) {
_isLayoutAnimationEnabled = isLayoutAnimationEnabled;
}

private:
static bool _isLayoutAnimationEnabled;
};

} // namespace reanimated
1 change: 1 addition & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ add_library(
"./src/main/Common/cpp/Tools/RuntimeDecorator.cpp"
"./src/main/Common/cpp/Tools/Scheduler.cpp"
"./src/main/Common/cpp/Tools/WorkletEventHandler.cpp"
"./src/main/Common/cpp/Tools/FeaturesConfig.cpp"
"./src/main/Common/cpp/LayoutAnimations/LayoutAnimationsProxy.cpp"
)

Expand Down
8 changes: 8 additions & 0 deletions android/src/main/cpp/LayoutAnimations.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "LayoutAnimations.h"
#include "FeaturesConfig.h"
#include "Logger.h"

namespace reanimated {
Expand Down Expand Up @@ -82,13 +83,20 @@ void LayoutAnimations::notifyAboutEnd(int tag, int cancelled) {
method(javaPart_.get(), tag, cancelled);
}

bool LayoutAnimations::isLayoutAnimationEnabled() {
return FeaturesConfig::isLayoutAnimationEnabled();
}

void LayoutAnimations::registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", LayoutAnimations::initHybrid),
makeNativeMethod(
"startAnimationForTag", LayoutAnimations::startAnimationForTag),
makeNativeMethod(
"removeConfigForTag", LayoutAnimations::removeConfigForTag),
makeNativeMethod(
"isLayoutAnimationEnabled",
LayoutAnimations::isLayoutAnimationEnabled),
});
}

Expand Down
1 change: 1 addition & 0 deletions android/src/main/cpp/headers/LayoutAnimations.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class LayoutAnimations : public jni::HybridClass<LayoutAnimations> {
alias_ref<JString> type,
alias_ref<JMap<jstring, jstring>> values);
void removeConfigForTag(int tag);
bool isLayoutAnimationEnabled();

void setWeakUIRuntime(std::weak_ptr<jsi::Runtime> wrt);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ public void removeConfigForTag(int tag) {
LayoutAnimations.removeConfigForTag(tag);
}
}

@Override
public boolean isLayoutAnimationEnabled() {
return LayoutAnimations.isLayoutAnimationEnabled();
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -508,4 +508,8 @@ public void updateLayout(
viewToUpdate.layout(x, y, x + width, y + height);
}
}

public boolean isLayoutAnimationEnabled() {
return mNativeMethodsHolder.isLayoutAnimationEnabled();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public LayoutAnimations(ReactApplicationContext context) {

public native void removeConfigForTag(int tag);

public native boolean isLayoutAnimationEnabled();

private void notifyAboutEnd(int tag, int cancelledInt) {
ReactApplicationContext context = mContext.get();
if (context != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ public interface NativeMethodsHolder {
public void startAnimationForTag(int tag, String type, HashMap<String, Float> values);

public void removeConfigForTag(int tag);

public boolean isLayoutAnimationEnabled();
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public void maybeInit() {
}

public boolean shouldAnimateLayout(View viewToAnimate) {
if (!isLayoutAnimationEnabled()) {
return super.shouldAnimateLayout(viewToAnimate);
}
// if view parent is null, skip animation: view have been clipped, we don't want animation to
// resume when view is re-attached to parent, which is the standard android animation behavior.
// If there's a layout handling animation going on, it should be animated nonetheless since the
Expand All @@ -69,6 +72,10 @@ public boolean shouldAnimateLayout(View viewToAnimate) {
* @param height the new height value for the view
*/
public void applyLayoutUpdate(View view, int x, int y, int width, int height) {
if (!isLayoutAnimationEnabled()) {
super.applyLayoutUpdate(view, x, y, width, height);
return;
}
UiThreadUtil.assertOnUiThread();
maybeInit();
// Determine which animation to use : if view is initially invisible, use create animation,
Expand Down Expand Up @@ -99,6 +106,10 @@ public void applyLayoutUpdate(View view, int x, int y, int width, int height) {
* view.
*/
public void deleteView(final View view, final LayoutAnimationListener listener) {
if (!isLayoutAnimationEnabled()) {
super.deleteView(view, listener);
return;
}
UiThreadUtil.assertOnUiThread();
NativeViewHierarchyManager nativeViewHierarchyManager = mWeakNativeViewHierarchyManage.get();
ViewManager viewManager;
Expand Down Expand Up @@ -174,6 +185,11 @@ private void dfs(View view, NativeViewHierarchyManager nativeViewHierarchyManage
}
}
}

public boolean isLayoutAnimationEnabled() {
maybeInit();
return mAnimationsManager.isLayoutAnimationEnabled();
}
}

public class ReanimatedNativeHierarchyManager extends NativeViewHierarchyManager {
Expand Down Expand Up @@ -217,6 +233,9 @@ public ReanimatedNativeHierarchyManager(
public synchronized void updateLayout(
int parentTag, int tag, int x, int y, int width, int height) {
super.updateLayout(parentTag, tag, x, y, width, height);
if (!((ReaLayoutAnimator) mReaLayoutAnimator).isLayoutAnimationEnabled()) {
return;
}
try {
View viewToUpdate = this.resolveView(tag);
ViewManager viewManager = this.resolveViewManager(tag);
Expand Down Expand Up @@ -244,6 +263,10 @@ public synchronized void manageChildren(
@Nullable int[] indicesToRemove,
@Nullable ViewAtIndex[] viewsToAdd,
@Nullable int[] tagsToDelete) {
if (!((ReaLayoutAnimator) mReaLayoutAnimator).isLayoutAnimationEnabled()) {
super.manageChildren(tag, indicesToRemove, viewsToAdd, tagsToDelete);
return;
}
ViewGroup viewGroup;
ViewGroupManager viewGroupManager;
try {
Expand Down Expand Up @@ -329,6 +352,10 @@ public void publicDropView(View view) {

@Override
protected synchronized void dropView(View view) {
if (!((ReaLayoutAnimator) mReaLayoutAnimator).isLayoutAnimationEnabled()) {
super.dropView(view);
return;
}
if (toBeRemoved.containsKey(view.getId())) {
toBeRemoved.remove(view.getId());
}
Expand Down
41 changes: 34 additions & 7 deletions ios/LayoutReanimation/REAUIManager.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import "REAUIManager.h"
#import <Foundation/Foundation.h>
#include "FeaturesConfig.h"
#import "RCTComponentData.h"
#import "RCTLayoutAnimation.h"
#import "RCTLayoutAnimationGroup.h"
Expand Down Expand Up @@ -71,6 +72,18 @@ - (void)_manageChildren:(NSNumber *)containerTag
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
{
if (!reanimated::FeaturesConfig::isLayoutAnimationEnabled()) {
[super _manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
addAtIndices:addAtIndices
removeAtIndices:removeAtIndices
registry:registry];
return;
}

// Reanimated changes /start
BOOL isUIViewRegistry = ((id)registry == (id)[self valueForKey:@"_viewRegistry"]);
id<RCTComponent> container;
NSMutableArray<id<RCTComponent>> *permanentlyRemovedChildren;
Expand All @@ -91,6 +104,7 @@ - (void)_manageChildren:(NSNumber *)containerTag
}
}
}
// Reanimated changes /end

[super _manageChildren:containerTag
moveFromIndices:moveFromIndices
Expand All @@ -100,6 +114,7 @@ - (void)_manageChildren:(NSNumber *)containerTag
removeAtIndices:removeAtIndices
registry:registry];

// Reanimated changes /start
if (isUIViewRegistry) {
NSMutableDictionary<NSNumber *, id<RCTComponent>> *viewRegistry = [self valueForKey:@"_viewRegistry"];
for (id<RCTComponent> toRemoveChild in _toBeRemovedRegister[containerTag]) {
Expand Down Expand Up @@ -127,6 +142,7 @@ - (void)_manageChildren:(NSNumber *)containerTag
[self callAnimationForTree:removedChild parentTag:containerTag];
}
}
// Reanimated changes /end
}

- (void)callAnimationForTree:(UIView *)view parentTag:(NSNumber *)parentTag
Expand Down Expand Up @@ -253,7 +269,13 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *
view.hidden = isHidden;
}

REASnapshot *snapshotBefore = [[REASnapshot alloc] init:view];
// Reanimated changes /start
REASnapshot *snapshotBefore;
if (reanimated::FeaturesConfig::isLayoutAnimationEnabled()) {
snapshotBefore = [[REASnapshot alloc] init:view];
}
// Reanimated changes /end

if (creatingLayoutAnimation) {
// Animate view creation
[view reactSetFrame:frame];
Expand Down Expand Up @@ -299,18 +321,23 @@ - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *
completion(YES);
}

if (isNew) {
REASnapshot *snapshot = [[REASnapshot alloc] init:view];
[_animationsManager onViewCreate:view after:snapshot];
} else {
REASnapshot *snapshotAfter = [[REASnapshot alloc] init:view];
[_animationsManager onViewUpdate:view before:snapshotBefore after:snapshotAfter];
// Reanimated changes /start
if (reanimated::FeaturesConfig::isLayoutAnimationEnabled()) {
if (isNew) {
REASnapshot *snapshot = [[REASnapshot alloc] init:view];
[_animationsManager onViewCreate:view after:snapshot];
} else {
REASnapshot *snapshotAfter = [[REASnapshot alloc] init:view];
[_animationsManager onViewUpdate:view before:snapshotBefore after:snapshotAfter];
}
}
}

[_animationsManager removeLeftovers];
// Clean up
// uiManager->_layoutAnimationGroup = nil;
[uiManager setValue:nil forKey:@"_layoutAnimationGroup"];
// Reanimated changes /end
};
}

Expand Down
4 changes: 3 additions & 1 deletion ios/native/NativeProxy.mm
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &v
throw std::runtime_error("Unsupported jsi::jsi::Value kind");
}

std::shared_ptr<NativeReanimatedModule> createReanimatedModule(RCTBridge *bridge, std::shared_ptr<CallInvoker> jsInvoker)
std::shared_ptr<NativeReanimatedModule> createReanimatedModule(
RCTBridge *bridge,
std::shared_ptr<CallInvoker> jsInvoker)
{
REAModule *reanimatedModule = [bridge moduleForClass:[REAModule class]];

Expand Down
1 change: 1 addition & 0 deletions react-native-reanimated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1071,4 +1071,5 @@ declare module 'react-native-reanimated' {
export const SpringUtils: typeof Animated.SpringUtils;
export const useValue: typeof Animated.useValue;
export const ReverseAnimation: typeof Animated.ReverseAnimation;
export function enableLayoutAnimations(flag: boolean): void;
}
15 changes: 13 additions & 2 deletions src/createAnimatedComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@ import './reanimated2/layoutReanimation/LayoutAnimationRepository';
import invariant from 'invariant';
import { adaptViewConfig } from './ConfigHelper';
import { RNRenderer } from './reanimated2/platform-specific/RNRenderer';
import { makeMutable, runOnUI } from './reanimated2/core';
import {
makeMutable,
runOnUI,
enableLayoutAnimations,
} from './reanimated2/core';
import {
DefaultEntering,
DefaultExiting,
DefaultLayout,
} from './reanimated2/layoutReanimation/defaultAnimations/Default';
import { isJest, isChromeDebugger } from './reanimated2/PlatformChecker';
import {
isJest,
isChromeDebugger,
shouldBeUseWeb,
} from './reanimated2/PlatformChecker';
import { initialUpdaterRun } from './reanimated2/animation';
import {
BaseAnimationBuilder,
Expand Down Expand Up @@ -520,6 +528,9 @@ export default function createAnimatedComponent(
(this.props.layout || this.props.entering || this.props.exiting) &&
tag != null
) {
if (!shouldBeUseWeb()) {
enableLayoutAnimations(true, false);
}
let layout = this.props.layout ? this.props.layout : DefaultLayout;
let entering = this.props.entering
? this.props.entering
Expand Down
Loading

0 comments on commit 5a1407c

Please sign in to comment.