diff --git a/Examples/UIExplorer/js/ListViewPagingExample.js b/Examples/UIExplorer/js/ListViewPagingExample.js index 07d8f802c..58d7359e5 100644 --- a/Examples/UIExplorer/js/ListViewPagingExample.js +++ b/Examples/UIExplorer/js/ListViewPagingExample.js @@ -58,6 +58,11 @@ var NUM_SECTIONS = 100; var NUM_ROWS_PER_SECTION = 10; class Thumb extends React.Component { + componentWillMount() { + UIManager.setLayoutAnimationEnabledExperimental && + UIManager.setLayoutAnimationEnabledExperimental(true); + } + _getThumbIdx = () => { return Math.floor(Math.random() * THUMB_URLS.length); }; diff --git a/Examples/UIExplorer/js/UIExplorerApp.android.js b/Examples/UIExplorer/js/UIExplorerApp.android.js index a6ca080e1..1e13a212c 100644 --- a/Examples/UIExplorer/js/UIExplorerApp.android.js +++ b/Examples/UIExplorer/js/UIExplorerApp.android.js @@ -46,6 +46,8 @@ const nativeImageSource = require('nativeImageSource'); import type { UIExplorerNavigationState } from './UIExplorerNavigationReducer'; +UIManager.setLayoutAnimationEnabledExperimental(true); + const DRAWER_WIDTH_LEFT = 56; type Props = { diff --git a/Libraries/LayoutAnimation/LayoutAnimation.js b/Libraries/LayoutAnimation/LayoutAnimation.js index b1d8e6e87..61d62601d 100644 --- a/Libraries/LayoutAnimation/LayoutAnimation.js +++ b/Libraries/LayoutAnimation/LayoutAnimation.js @@ -88,6 +88,10 @@ function create(duration: number, type, creationProp): Config { update: { type, }, + delete: { + type, + property: creationProp, + }, }; } @@ -108,6 +112,10 @@ var Presets = { type: Types.spring, springDamping: 0.4, }, + delete: { + type: Types.linear, + property: Properties.opacity, + }, }, }; @@ -116,6 +124,10 @@ var Presets = { * next layout happens. * * A common way to use this API is to call it before calling `setState`. + * + * Note that in order to get this to work on **Android** you need to set the following flags via `UIManager`: + * + * UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); */ var LayoutAnimation = { /** diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java index 29f8f17cf..a5b157d0f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/NativeViewHierarchyManager.java @@ -76,6 +76,8 @@ public class NativeViewHierarchyManager { private final RootViewManager mRootViewManager; private final LayoutAnimationController mLayoutAnimator = new LayoutAnimationController(); + private boolean mLayoutAnimationEnabled; + public NativeViewHierarchyManager(ViewManagerRegistry viewManagers) { this(viewManagers, new RootViewManager()); } @@ -110,6 +112,10 @@ public class NativeViewHierarchyManager { return mAnimationRegistry; } + public void setLayoutAnimationEnabled(boolean enabled) { + mLayoutAnimationEnabled = enabled; + } + public void updateProperties(int tag, ReactStylesDiffMap props) { UiThreadUtil.assertOnUiThread(); @@ -186,7 +192,8 @@ public class NativeViewHierarchyManager { } private void updateLayout(View viewToUpdate, int x, int y, int width, int height) { - if (mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) { + if (mLayoutAnimationEnabled && + mLayoutAnimator.shouldAnimateLayout(viewToUpdate)) { mLayoutAnimator.applyLayoutUpdate(viewToUpdate, x, y, width, height); } else { viewToUpdate.layout(x, y, x + width, y + height); @@ -356,7 +363,8 @@ public class NativeViewHierarchyManager { View viewToRemove = viewManager.getChildAt(viewToManage, indexToRemove); - if (mLayoutAnimator.shouldAnimateLayout(viewToRemove) && + if (mLayoutAnimationEnabled && + mLayoutAnimator.shouldAnimateLayout(viewToRemove) && arrayContains(tagsToDelete, viewToRemove.getId())) { // The view will be removed and dropped by the 'delete' layout animation // instead, so do nothing @@ -403,7 +411,8 @@ public class NativeViewHierarchyManager { tagsToDelete)); } - if (mLayoutAnimator.shouldAnimateLayout(viewToDestroy)) { + if (mLayoutAnimationEnabled && + mLayoutAnimator.shouldAnimateLayout(viewToDestroy)) { mLayoutAnimator.deleteView(viewToDestroy, new LayoutAnimationListener() { @Override public void onAnimationEnd() { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java index 78a03fa26..e6499c43d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java @@ -569,6 +569,21 @@ public class UIImplementation { mOperationsQueue.enqueueRemoveAnimation(animationID); } + /** + * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled + * explicitly in order to avoid regression in existing application written for iOS using this API. + * + * Warning : This method will be removed in future version of React Native, and layout animation + * will be enabled by default, so always check for its existence before invoking it. + * + * TODO(9139831) : remove this method once layout animation is fully stable. + * + * @param enabled whether layout animation is enabled or not + */ + public void setLayoutAnimationEnabledExperimental(boolean enabled) { + mOperationsQueue.enqueueSetLayoutAnimationEnabled(enabled); + } + /** * Configure an animation to be used for the native layout changes, and native views * creation. The animation will only apply during the current batch operations. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index b33b3b598..1d05aa267 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -474,6 +474,22 @@ public class UIManagerModule extends ReactContextBaseJavaModule implements mUIImplementation.showPopupMenu(reactTag, items, error, success); } + /** + * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled + * explicitly in order to avoid regression in existing application written for iOS using this API. + * + * Warning : This method will be removed in future version of React Native, and layout animation + * will be enabled by default, so always check for its existence before invoking it. + * + * TODO(9139831) : remove this method once layout animation is fully stable. + * + * @param enabled whether layout animation is enabled or not + */ + @ReactMethod + public void setLayoutAnimationEnabledExperimental(boolean enabled) { + mUIImplementation.setLayoutAnimationEnabledExperimental(enabled); + } + /** * Configure an animation to be used for the native layout changes, and native views * creation. The animation will only apply during the current batch operations. diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java index a11766cdf..7098bf057 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIViewOperationQueue.java @@ -351,6 +351,19 @@ public class UIViewOperationQueue { } } + private class SetLayoutAnimationEnabledOperation implements UIOperation { + private final boolean mEnabled; + + private SetLayoutAnimationEnabledOperation(final boolean enabled) { + mEnabled = enabled; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.setLayoutAnimationEnabled(mEnabled); + } + } + private class ConfigureLayoutAnimationOperation implements UIOperation { private final ReadableMap mConfig; @@ -686,6 +699,11 @@ public class UIViewOperationQueue { mOperations.add(new RemoveAnimationOperation(animationID)); } + public void enqueueSetLayoutAnimationEnabled( + final boolean enabled) { + mOperations.add(new SetLayoutAnimationEnabledOperation(enabled)); + } + public void enqueueConfigureLayoutAnimation( final ReadableMap config, final Callback onSuccess, diff --git a/docs/Animations.md b/docs/Animations.md index 30514f315..c97fe2b46 100644 --- a/docs/Animations.md +++ b/docs/Animations.md @@ -387,6 +387,12 @@ it provides much less control than `Animated` and other animation libraries, so you may need to use another approach if you can't get `LayoutAnimation` to do what you want. +Note that in order to get this to work on **Android** you need to set the following flags via `UIManager`: + +```javascript +UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); +``` + ![](img/LayoutAnimationExample.gif) ```javascript