mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-29 04:35:36 +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(
|
UIManager.configureNextLayoutAnimation(
|
||||||
config,
|
config,
|
||||||
onAnimationDidEnd ?? function() {},
|
onAnimationDidEnd ?? function() {},
|
||||||
function() {
|
function() {} /* unused onError */,
|
||||||
/* unused */
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ class AddRemoveExample extends React.Component<{}, $FlowFixMeState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UNSAFE_componentWillUpdate() {
|
UNSAFE_componentWillUpdate() {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut(args =>
|
||||||
|
console.log('AddRemoveExample completed', args),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPressAddView = () => {
|
_onPressAddView = () => {
|
||||||
@@ -73,7 +75,9 @@ class CrossFadeExample extends React.Component<{}, $FlowFixMeState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_onPressToggle = () => {
|
_onPressToggle = () => {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut(args =>
|
||||||
|
console.log('CrossFadeExample completed', args),
|
||||||
|
);
|
||||||
this.setState(state => ({toggled: !state.toggled}));
|
this.setState(state => ({toggled: !state.toggled}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -116,12 +120,15 @@ class LayoutUpdateExample extends React.Component<{}, $FlowFixMeState> {
|
|||||||
this._clearTimeout();
|
this._clearTimeout();
|
||||||
this.setState({width: 150});
|
this.setState({width: 150});
|
||||||
|
|
||||||
LayoutAnimation.configureNext({
|
LayoutAnimation.configureNext(
|
||||||
duration: 1000,
|
{
|
||||||
update: {
|
duration: 1000,
|
||||||
type: LayoutAnimation.Types.linear,
|
update: {
|
||||||
|
type: LayoutAnimation.Types.linear,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
args => console.log('LayoutUpdateExample completed', args),
|
||||||
|
);
|
||||||
|
|
||||||
this.timeout = setTimeout(() => this.setState({width: 100}), 500);
|
this.timeout = setTimeout(() => this.setState({width: 100}), 500);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -729,8 +729,8 @@ public class NativeViewHierarchyManager {
|
|||||||
mJSResponderHandler.clearJSResponder();
|
mJSResponderHandler.clearJSResponder();
|
||||||
}
|
}
|
||||||
|
|
||||||
void configureLayoutAnimation(final ReadableMap config) {
|
void configureLayoutAnimation(final ReadableMap config, final Callback onAnimationComplete) {
|
||||||
mLayoutAnimator.initializeFromConfig(config);
|
mLayoutAnimator.initializeFromConfig(config, onAnimationComplete);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearLayoutAnimation() {
|
void clearLayoutAnimation() {
|
||||||
|
|||||||
@@ -720,11 +720,8 @@ public class UIImplementation {
|
|||||||
* interrupted. In this case, callback parameter will be false.
|
* interrupted. In this case, callback parameter will be false.
|
||||||
* @param error will be called if there was an error processing the animation
|
* @param error will be called if there was an error processing the animation
|
||||||
*/
|
*/
|
||||||
public void configureNextLayoutAnimation(
|
public void configureNextLayoutAnimation(ReadableMap config, Callback success) {
|
||||||
ReadableMap config,
|
mOperationsQueue.enqueueConfigureLayoutAnimation(config, success);
|
||||||
Callback success,
|
|
||||||
Callback error) {
|
|
||||||
mOperationsQueue.enqueueConfigureLayoutAnimation(config, success, error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setJSResponder(int reactTag, boolean blockNativeResponder) {
|
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
|
* 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.
|
* animation will only apply during the current batch operations.
|
||||||
*
|
*
|
||||||
* <p>TODO(7728153) : animating view deletion is currently not supported. TODO(7613721) :
|
* <p>TODO(7728153) : animating view deletion is currently not supported.
|
||||||
* callbacks are not supported, this feature will likely be killed.
|
|
||||||
*
|
*
|
||||||
* @param config the configuration of the animation for view addition/removal/update.
|
* @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
|
* @param success will be called when the animation completes, or when the animation get
|
||||||
@@ -719,7 +718,7 @@ public class UIManagerModule extends ReactContextBaseJavaModule
|
|||||||
*/
|
*/
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void configureNextLayoutAnimation(ReadableMap config, Callback success, Callback error) {
|
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 class ConfigureLayoutAnimationOperation implements UIOperation {
|
||||||
private final ReadableMap mConfig;
|
private final ReadableMap mConfig;
|
||||||
|
private final Callback mAnimationComplete;
|
||||||
|
|
||||||
private ConfigureLayoutAnimationOperation(final ReadableMap config) {
|
private ConfigureLayoutAnimationOperation(final ReadableMap config, final Callback animationComplete) {
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
|
mAnimationComplete = animationComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() {
|
public void execute() {
|
||||||
mNativeViewHierarchyManager.configureLayoutAnimation(mConfig);
|
mNativeViewHierarchyManager.configureLayoutAnimation(mConfig, mAnimationComplete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -741,9 +743,8 @@ public class UIViewOperationQueue {
|
|||||||
|
|
||||||
public void enqueueConfigureLayoutAnimation(
|
public void enqueueConfigureLayoutAnimation(
|
||||||
final ReadableMap config,
|
final ReadableMap config,
|
||||||
final Callback onSuccess,
|
final Callback onAnimationComplete) {
|
||||||
final Callback onError) {
|
mOperations.add(new ConfigureLayoutAnimationOperation(config, onAnimationComplete));
|
||||||
mOperations.add(new ConfigureLayoutAnimationOperation(config));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enqueueMeasure(
|
public void enqueueMeasure(
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ package com.facebook.react.uimanager.layoutanimation;
|
|||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.annotation.concurrent.NotThreadSafe;
|
import javax.annotation.concurrent.NotThreadSafe;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.Callback;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.bridge.UiThreadUtil;
|
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
|
* 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
|
* supplied. If not animation is available, layout change is applied immediately instead of
|
||||||
* performing an animation.
|
* performing an animation.
|
||||||
*
|
|
||||||
* TODO(7613721): Invoke success callback at the end of animation and when animation gets cancelled.
|
|
||||||
*/
|
*/
|
||||||
@NotThreadSafe
|
@NotThreadSafe
|
||||||
public class LayoutAnimationController {
|
public class LayoutAnimationController {
|
||||||
|
|
||||||
private static final boolean ENABLED = true;
|
|
||||||
|
|
||||||
private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation();
|
private final AbstractLayoutAnimation mLayoutCreateAnimation = new LayoutCreateAnimation();
|
||||||
private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation();
|
private final AbstractLayoutAnimation mLayoutUpdateAnimation = new LayoutUpdateAnimation();
|
||||||
private final AbstractLayoutAnimation mLayoutDeleteAnimation = new LayoutDeleteAnimation();
|
private final AbstractLayoutAnimation mLayoutDeleteAnimation = new LayoutDeleteAnimation();
|
||||||
private final SparseArray<LayoutHandlingAnimation> mLayoutHandlers = new SparseArray<>(0);
|
private final SparseArray<LayoutHandlingAnimation> mLayoutHandlers = new SparseArray<>(0);
|
||||||
|
|
||||||
private boolean mShouldAnimateLayout;
|
private boolean mShouldAnimateLayout;
|
||||||
|
private long mMaxAnimationDuration = -1;
|
||||||
|
@Nullable private Runnable mCompletionRunnable;
|
||||||
|
|
||||||
public void initializeFromConfig(final @Nullable ReadableMap config) {
|
@Nullable private static Handler sCompletionHandler;
|
||||||
if (!ENABLED) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public void initializeFromConfig(final @Nullable ReadableMap config, final Callback completionCallback) {
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
reset();
|
reset();
|
||||||
return;
|
return;
|
||||||
@@ -61,13 +61,24 @@ public class LayoutAnimationController {
|
|||||||
config.getMap(LayoutAnimationType.toString(LayoutAnimationType.DELETE)), globalDuration);
|
config.getMap(LayoutAnimationType.toString(LayoutAnimationType.DELETE)), globalDuration);
|
||||||
mShouldAnimateLayout = true;
|
mShouldAnimateLayout = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mShouldAnimateLayout && completionCallback != null) {
|
||||||
|
mCompletionRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
completionCallback.invoke(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
mLayoutCreateAnimation.reset();
|
mLayoutCreateAnimation.reset();
|
||||||
mLayoutUpdateAnimation.reset();
|
mLayoutUpdateAnimation.reset();
|
||||||
mLayoutDeleteAnimation.reset();
|
mLayoutDeleteAnimation.reset();
|
||||||
|
mCompletionRunnable = null;
|
||||||
mShouldAnimateLayout = false;
|
mShouldAnimateLayout = false;
|
||||||
|
mMaxAnimationDuration = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean shouldAnimateLayout(View viewToAnimate) {
|
public boolean shouldAnimateLayout(View viewToAnimate) {
|
||||||
@@ -94,10 +105,10 @@ public class LayoutAnimationController {
|
|||||||
UiThreadUtil.assertOnUiThread();
|
UiThreadUtil.assertOnUiThread();
|
||||||
|
|
||||||
final int reactTag = view.getId();
|
final int reactTag = view.getId();
|
||||||
LayoutHandlingAnimation existingAnimation = mLayoutHandlers.get(reactTag);
|
|
||||||
|
|
||||||
// Update an ongoing animation if possible, otherwise the layout update would be ignored as
|
// Update an ongoing animation if possible, otherwise the layout update would be ignored as
|
||||||
// the existing animation would still animate to the old layout.
|
// the existing animation would still animate to the old layout.
|
||||||
|
LayoutHandlingAnimation existingAnimation = mLayoutHandlers.get(reactTag);
|
||||||
if (existingAnimation != null) {
|
if (existingAnimation != null) {
|
||||||
existingAnimation.onLayoutUpdate(x, y, width, height);
|
existingAnimation.onLayoutUpdate(x, y, width, height);
|
||||||
return;
|
return;
|
||||||
@@ -132,6 +143,12 @@ public class LayoutAnimationController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (animation != null) {
|
if (animation != null) {
|
||||||
|
long animationDuration = animation.getDuration();
|
||||||
|
if (animationDuration > mMaxAnimationDuration) {
|
||||||
|
mMaxAnimationDuration = animationDuration;
|
||||||
|
scheduleCompletionCallback(animationDuration);
|
||||||
|
}
|
||||||
|
|
||||||
view.startAnimation(animation);
|
view.startAnimation(animation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,9 +163,7 @@ public class LayoutAnimationController {
|
|||||||
public void deleteView(final View view, final LayoutAnimationListener listener) {
|
public void deleteView(final View view, final LayoutAnimationListener listener) {
|
||||||
UiThreadUtil.assertOnUiThread();
|
UiThreadUtil.assertOnUiThread();
|
||||||
|
|
||||||
AbstractLayoutAnimation layoutAnimation = mLayoutDeleteAnimation;
|
Animation animation = mLayoutDeleteAnimation.createAnimation(
|
||||||
|
|
||||||
Animation animation = layoutAnimation.createAnimation(
|
|
||||||
view, view.getLeft(), view.getTop(), view.getWidth(), view.getHeight());
|
view, view.getLeft(), view.getTop(), view.getWidth(), view.getHeight());
|
||||||
|
|
||||||
if (animation != null) {
|
if (animation != null) {
|
||||||
@@ -167,6 +182,12 @@ public class LayoutAnimationController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
long animationDuration = animation.getDuration();
|
||||||
|
if (animationDuration > mMaxAnimationDuration) {
|
||||||
|
scheduleCompletionCallback(animationDuration);
|
||||||
|
mMaxAnimationDuration = animationDuration;
|
||||||
|
}
|
||||||
|
|
||||||
view.startAnimation(animation);
|
view.startAnimation(animation);
|
||||||
} else {
|
} else {
|
||||||
listener.onAnimationEnd();
|
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