mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-01-12 22:50:10 +08:00
Implement completion callback for LayoutAnimation on Android
Summary: All animations are scheduled by the UIManager while it processes a batch of changes, so we can just wait to see what the longest animation is and cancel+reschedule the callback. Reviewed By: mdvacca Differential Revision: D14656733 fbshipit-source-id: 4cbbb7e741219cd43f511f2ce750c53c30e2b2ca
This commit is contained in:
committed by
Facebook Github Bot
parent
a333c2b202
commit
f571c62ddf
@@ -47,9 +47,7 @@ function configureNext(
|
||||
UIManager.configureNextLayoutAnimation(
|
||||
config,
|
||||
onAnimationDidEnd ?? function() {},
|
||||
function() {
|
||||
/* unused */
|
||||
},
|
||||
function() {} /* unused onError */,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ class AddRemoveExample extends React.Component<{}, $FlowFixMeState> {
|
||||
};
|
||||
|
||||
UNSAFE_componentWillUpdate() {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
LayoutAnimation.easeInEaseOut(args =>
|
||||
console.log('AddRemoveExample completed', args),
|
||||
);
|
||||
}
|
||||
|
||||
_onPressAddView = () => {
|
||||
@@ -73,7 +75,9 @@ class CrossFadeExample extends React.Component<{}, $FlowFixMeState> {
|
||||
};
|
||||
|
||||
_onPressToggle = () => {
|
||||
LayoutAnimation.easeInEaseOut();
|
||||
LayoutAnimation.easeInEaseOut(args =>
|
||||
console.log('CrossFadeExample completed', args),
|
||||
);
|
||||
this.setState(state => ({toggled: !state.toggled}));
|
||||
};
|
||||
|
||||
@@ -116,12 +120,15 @@ class LayoutUpdateExample extends React.Component<{}, $FlowFixMeState> {
|
||||
this._clearTimeout();
|
||||
this.setState({width: 150});
|
||||
|
||||
LayoutAnimation.configureNext({
|
||||
duration: 1000,
|
||||
update: {
|
||||
type: LayoutAnimation.Types.linear,
|
||||
LayoutAnimation.configureNext(
|
||||
{
|
||||
duration: 1000,
|
||||
update: {
|
||||
type: LayoutAnimation.Types.linear,
|
||||
},
|
||||
},
|
||||
});
|
||||
args => console.log('LayoutUpdateExample completed', args),
|
||||
);
|
||||
|
||||
this.timeout = setTimeout(() => this.setState({width: 100}), 500);
|
||||
};
|
||||
|
||||
@@ -729,8 +729,8 @@ public class NativeViewHierarchyManager {
|
||||
mJSResponderHandler.clearJSResponder();
|
||||
}
|
||||
|
||||
void configureLayoutAnimation(final ReadableMap config) {
|
||||
mLayoutAnimator.initializeFromConfig(config);
|
||||
void configureLayoutAnimation(final ReadableMap config, final Callback onAnimationComplete) {
|
||||
mLayoutAnimator.initializeFromConfig(config, onAnimationComplete);
|
||||
}
|
||||
|
||||
void clearLayoutAnimation() {
|
||||
|
||||
@@ -720,11 +720,8 @@ public class UIImplementation {
|
||||
* interrupted. In this case, callback parameter will be false.
|
||||
* @param error will be called if there was an error processing the animation
|
||||
*/
|
||||
public void configureNextLayoutAnimation(
|
||||
ReadableMap config,
|
||||
Callback success,
|
||||
Callback error) {
|
||||
mOperationsQueue.enqueueConfigureLayoutAnimation(config, success, error);
|
||||
public void configureNextLayoutAnimation(ReadableMap config, Callback success) {
|
||||
mOperationsQueue.enqueueConfigureLayoutAnimation(config, success);
|
||||
}
|
||||
|
||||
public void setJSResponder(int reactTag, boolean blockNativeResponder) {
|
||||
|
||||
@@ -709,8 +709,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule
|
||||
* 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.
|
||||
*
|
||||
* <p>TODO(7728153) : animating view deletion is currently not supported. TODO(7613721) :
|
||||
* callbacks are not supported, this feature will likely be killed.
|
||||
* <p>TODO(7728153) : animating view deletion is currently not supported.
|
||||
*
|
||||
* @param config the configuration of the animation for view addition/removal/update.
|
||||
* @param success will be called when the animation completes, or when the animation get
|
||||
@@ -719,7 +718,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule
|
||||
*/
|
||||
@ReactMethod
|
||||
public void configureNextLayoutAnimation(ReadableMap config, Callback success, Callback error) {
|
||||
mUIImplementation.configureNextLayoutAnimation(config, success, error);
|
||||
mUIImplementation.configureNextLayoutAnimation(config, success);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -369,14 +369,16 @@ public class UIViewOperationQueue {
|
||||
|
||||
private class ConfigureLayoutAnimationOperation implements UIOperation {
|
||||
private final ReadableMap mConfig;
|
||||
private final Callback mAnimationComplete;
|
||||
|
||||
private ConfigureLayoutAnimationOperation(final ReadableMap config) {
|
||||
private ConfigureLayoutAnimationOperation(final ReadableMap config, final Callback animationComplete) {
|
||||
mConfig = config;
|
||||
mAnimationComplete = animationComplete;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
mNativeViewHierarchyManager.configureLayoutAnimation(mConfig);
|
||||
mNativeViewHierarchyManager.configureLayoutAnimation(mConfig, mAnimationComplete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,9 +743,8 @@ public class UIViewOperationQueue {
|
||||
|
||||
public void enqueueConfigureLayoutAnimation(
|
||||
final ReadableMap config,
|
||||
final Callback onSuccess,
|
||||
final Callback onError) {
|
||||
mOperations.add(new ConfigureLayoutAnimationOperation(config));
|
||||
final Callback onAnimationComplete) {
|
||||
mOperations.add(new ConfigureLayoutAnimationOperation(config, onAnimationComplete));
|
||||
}
|
||||
|
||||
public void enqueueMeasure(
|
||||
|
||||
@@ -8,11 +8,14 @@ package com.facebook.react.uimanager.layoutanimation;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
|
||||
import com.facebook.react.bridge.Callback;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
|
||||
@@ -20,25 +23,22 @@ import com.facebook.react.bridge.UiThreadUtil;
|
||||
* Class responsible for animation layout changes, if a valid layout animation config has been
|
||||
* supplied. If not animation is available, layout change is applied immediately instead of
|
||||
* performing an animation.
|
||||
*
|
||||
* TODO(7613721): Invoke success callback at the end of animation and when animation gets cancelled.
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public class LayoutAnimationController {
|
||||
|
||||
private static final boolean ENABLED = true;
|
||||
|
||||
private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation();
|
||||
private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation();
|
||||
private final AbstractLayoutAnimation mLayoutDeleteAnimation = new LayoutDeleteAnimation();
|
||||
private final SparseArray<LayoutHandlingAnimation> mLayoutHandlers = new SparseArray<>(0);
|
||||
|
||||
private boolean mShouldAnimateLayout;
|
||||
private long mMaxAnimationDuration = -1;
|
||||
@Nullable private Runnable mCompletionRunnable;
|
||||
|
||||
public void initializeFromConfig(final @Nullable ReadableMap config) {
|
||||
if (!ENABLED) {
|
||||
return;
|
||||
}
|
||||
@Nullable private static Handler sCompletionHandler;
|
||||
|
||||
public void initializeFromConfig(final @Nullable ReadableMap config, final Callback completionCallback) {
|
||||
if (config == null) {
|
||||
reset();
|
||||
return;
|
||||
@@ -61,13 +61,24 @@ public class LayoutAnimationController {
|
||||
config.getMap(LayoutAnimationType.toString(LayoutAnimationType.DELETE)), globalDuration);
|
||||
mShouldAnimateLayout = true;
|
||||
}
|
||||
|
||||
if (mShouldAnimateLayout && completionCallback != null) {
|
||||
mCompletionRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
completionCallback.invoke(Boolean.TRUE);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
mLayoutCreateAnimation.reset();
|
||||
mLayoutUpdateAnimation.reset();
|
||||
mLayoutDeleteAnimation.reset();
|
||||
mCompletionRunnable = null;
|
||||
mShouldAnimateLayout = false;
|
||||
mMaxAnimationDuration = -1;
|
||||
}
|
||||
|
||||
public boolean shouldAnimateLayout(View viewToAnimate) {
|
||||
@@ -94,10 +105,10 @@ public class LayoutAnimationController {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
final int reactTag = view.getId();
|
||||
LayoutHandlingAnimation existingAnimation = mLayoutHandlers.get(reactTag);
|
||||
|
||||
// Update an ongoing animation if possible, otherwise the layout update would be ignored as
|
||||
// the existing animation would still animate to the old layout.
|
||||
LayoutHandlingAnimation existingAnimation = mLayoutHandlers.get(reactTag);
|
||||
if (existingAnimation != null) {
|
||||
existingAnimation.onLayoutUpdate(x, y, width, height);
|
||||
return;
|
||||
@@ -132,6 +143,12 @@ public class LayoutAnimationController {
|
||||
}
|
||||
|
||||
if (animation != null) {
|
||||
long animationDuration = animation.getDuration();
|
||||
if (animationDuration > mMaxAnimationDuration) {
|
||||
mMaxAnimationDuration = animationDuration;
|
||||
scheduleCompletionCallback(animationDuration);
|
||||
}
|
||||
|
||||
view.startAnimation(animation);
|
||||
}
|
||||
}
|
||||
@@ -146,9 +163,7 @@ public class LayoutAnimationController {
|
||||
public void deleteView(final View view, final LayoutAnimationListener listener) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
|
||||
AbstractLayoutAnimation layoutAnimation = mLayoutDeleteAnimation;
|
||||
|
||||
Animation animation = layoutAnimation.createAnimation(
|
||||
Animation animation = mLayoutDeleteAnimation.createAnimation(
|
||||
view, view.getLeft(), view.getTop(), view.getWidth(), view.getHeight());
|
||||
|
||||
if (animation != null) {
|
||||
@@ -167,6 +182,12 @@ public class LayoutAnimationController {
|
||||
}
|
||||
});
|
||||
|
||||
long animationDuration = animation.getDuration();
|
||||
if (animationDuration > mMaxAnimationDuration) {
|
||||
scheduleCompletionCallback(animationDuration);
|
||||
mMaxAnimationDuration = animationDuration;
|
||||
}
|
||||
|
||||
view.startAnimation(animation);
|
||||
} else {
|
||||
listener.onAnimationEnd();
|
||||
@@ -185,4 +206,15 @@ public class LayoutAnimationController {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleCompletionCallback(long delayMillis) {
|
||||
if (sCompletionHandler == null) {
|
||||
sCompletionHandler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
if (mCompletionRunnable != null) {
|
||||
sCompletionHandler.removeCallbacks(mCompletionRunnable);
|
||||
sCompletionHandler.postDelayed(mCompletionRunnable, delayMillis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user