Proper NativeAnimated node invalidation on Android
Summary: This diff attempts to fix a number of Android native animation bugs related to incomplete node invalidation, e.g. https://github.com/facebook/react-native/pull/10657#issuecomment-258297467. For full correctness, we should mark any node as needing update when it is: - created - updated (value nodes) - attached to new parents - detached from old parents - attached to a view (prop nodes) cc/ janicduplessis Closes https://github.com/facebook/react-native/pull/10837 Differential Revision: D4166446 fbshipit-source-id: dbf6b9aa34439e286234627791bb7fef647c8396
This commit is contained in:
parent
617432c8c3
commit
6f5433febe
|
@ -49,8 +49,8 @@ import javax.annotation.Nullable;
|
|||
/*package*/ class NativeAnimatedNodesManager implements EventDispatcherListener {
|
||||
|
||||
private final SparseArray<AnimatedNode> mAnimatedNodes = new SparseArray<>();
|
||||
private final ArrayList<AnimationDriver> mActiveAnimations = new ArrayList<>();
|
||||
private final ArrayList<AnimatedNode> mUpdatedNodes = new ArrayList<>();
|
||||
private final SparseArray<AnimationDriver> mActiveAnimations = new SparseArray<>();
|
||||
private final SparseArray<AnimatedNode> mUpdatedNodes = new SparseArray<>();
|
||||
private final Map<String, EventAnimationDriver> mEventDrivers = new HashMap<>();
|
||||
private final Map<String, Map<String, String>> mCustomEventTypes;
|
||||
private final UIImplementation mUIImplementation;
|
||||
|
@ -68,7 +68,7 @@ import javax.annotation.Nullable;
|
|||
}
|
||||
|
||||
public boolean hasActiveAnimations() {
|
||||
return !mActiveAnimations.isEmpty() || !mUpdatedNodes.isEmpty();
|
||||
return mActiveAnimations.size() > 0 || mUpdatedNodes.size() > 0;
|
||||
}
|
||||
|
||||
public void createAnimatedNode(int tag, ReadableMap config) {
|
||||
|
@ -82,7 +82,6 @@ import javax.annotation.Nullable;
|
|||
node = new StyleAnimatedNode(config, this);
|
||||
} else if ("value".equals(type)) {
|
||||
node = new ValueAnimatedNode(config);
|
||||
mUpdatedNodes.add(node);
|
||||
} else if ("props".equals(type)) {
|
||||
node = new PropsAnimatedNode(config, this);
|
||||
} else if ("interpolation".equals(type)) {
|
||||
|
@ -104,10 +103,12 @@ import javax.annotation.Nullable;
|
|||
}
|
||||
node.mTag = tag;
|
||||
mAnimatedNodes.put(tag, node);
|
||||
mUpdatedNodes.put(tag, node);
|
||||
}
|
||||
|
||||
public void dropAnimatedNode(int tag) {
|
||||
mAnimatedNodes.remove(tag);
|
||||
mUpdatedNodes.remove(tag);
|
||||
}
|
||||
|
||||
public void startListeningToAnimatedNodeValue(int tag, AnimatedNodeValueListener listener) {
|
||||
|
@ -135,7 +136,7 @@ import javax.annotation.Nullable;
|
|||
" does not exists or is not a 'value' node");
|
||||
}
|
||||
((ValueAnimatedNode) node).mValue = value;
|
||||
mUpdatedNodes.add(node);
|
||||
mUpdatedNodes.put(tag, node);
|
||||
}
|
||||
|
||||
public void setAnimatedNodeOffset(int tag, double offset) {
|
||||
|
@ -145,7 +146,7 @@ import javax.annotation.Nullable;
|
|||
" does not exists or is not a 'value' node");
|
||||
}
|
||||
((ValueAnimatedNode) node).mOffset = offset;
|
||||
mUpdatedNodes.add(node);
|
||||
mUpdatedNodes.put(tag, node);
|
||||
}
|
||||
|
||||
public void flattenAnimatedNodeOffset(int tag) {
|
||||
|
@ -194,7 +195,7 @@ import javax.annotation.Nullable;
|
|||
animation.mId = animationId;
|
||||
animation.mEndCallback = endCallback;
|
||||
animation.mAnimatedValue = (ValueAnimatedNode) node;
|
||||
mActiveAnimations.add(animation);
|
||||
mActiveAnimations.put(animationId, animation);
|
||||
}
|
||||
|
||||
public void stopAnimation(int animationId) {
|
||||
|
@ -203,13 +204,13 @@ import javax.annotation.Nullable;
|
|||
// object map that would require additional memory just to support the use-case of stopping
|
||||
// an animation
|
||||
for (int i = 0; i < mActiveAnimations.size(); i++) {
|
||||
AnimationDriver animation = mActiveAnimations.get(i);
|
||||
AnimationDriver animation = mActiveAnimations.valueAt(i);
|
||||
if (animation.mId == animationId) {
|
||||
// Invoke animation end callback with {finished: false}
|
||||
WritableMap endCallbackResponse = Arguments.createMap();
|
||||
endCallbackResponse.putBoolean("finished", false);
|
||||
animation.mEndCallback.invoke(endCallbackResponse);
|
||||
mActiveAnimations.remove(i);
|
||||
mActiveAnimations.removeAt(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -231,6 +232,7 @@ import javax.annotation.Nullable;
|
|||
" does not exists");
|
||||
}
|
||||
parentNode.addChild(childNode);
|
||||
mUpdatedNodes.put(childNodeTag, childNode);
|
||||
}
|
||||
|
||||
public void disconnectAnimatedNodes(int parentNodeTag, int childNodeTag) {
|
||||
|
@ -245,6 +247,7 @@ import javax.annotation.Nullable;
|
|||
" does not exists");
|
||||
}
|
||||
parentNode.removeChild(childNode);
|
||||
mUpdatedNodes.put(childNodeTag, childNode);
|
||||
}
|
||||
|
||||
public void connectAnimatedNodeToView(int animatedNodeTag, int viewTag) {
|
||||
|
@ -263,6 +266,7 @@ import javax.annotation.Nullable;
|
|||
"already attached to a view");
|
||||
}
|
||||
propsAnimatedNode.mConnectedViewTag = viewTag;
|
||||
mUpdatedNodes.put(animatedNodeTag, node);
|
||||
}
|
||||
|
||||
public void disconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag) {
|
||||
|
@ -327,7 +331,7 @@ import javax.annotation.Nullable;
|
|||
EventAnimationDriver eventDriver = mEventDrivers.get(event.getViewTag() + eventName);
|
||||
if (eventDriver != null) {
|
||||
event.dispatch(eventDriver);
|
||||
mUpdatedNodes.add(eventDriver.mValueNode);
|
||||
mUpdatedNodes.put(eventDriver.mValueNode.mTag, eventDriver.mValueNode);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -368,7 +372,7 @@ import javax.annotation.Nullable;
|
|||
|
||||
Queue<AnimatedNode> nodesQueue = new ArrayDeque<>();
|
||||
for (int i = 0; i < mUpdatedNodes.size(); i++) {
|
||||
AnimatedNode node = mUpdatedNodes.get(i);
|
||||
AnimatedNode node = mUpdatedNodes.valueAt(i);
|
||||
if (node.mBFSColor != mAnimatedGraphBFSColor) {
|
||||
node.mBFSColor = mAnimatedGraphBFSColor;
|
||||
activeNodesCount++;
|
||||
|
@ -377,7 +381,7 @@ import javax.annotation.Nullable;
|
|||
}
|
||||
|
||||
for (int i = 0; i < mActiveAnimations.size(); i++) {
|
||||
AnimationDriver animation = mActiveAnimations.get(i);
|
||||
AnimationDriver animation = mActiveAnimations.valueAt(i);
|
||||
animation.runAnimationStep(frameTimeNanos);
|
||||
AnimatedNode valueNode = animation.mAnimatedValue;
|
||||
if (valueNode.mBFSColor != mAnimatedGraphBFSColor) {
|
||||
|
@ -422,7 +426,7 @@ import javax.annotation.Nullable;
|
|||
// find nodes with zero "incoming nodes", those can be either nodes from `mUpdatedNodes` or
|
||||
// ones connected to active animations
|
||||
for (int i = 0; i < mUpdatedNodes.size(); i++) {
|
||||
AnimatedNode node = mUpdatedNodes.get(i);
|
||||
AnimatedNode node = mUpdatedNodes.valueAt(i);
|
||||
if (node.mActiveIncomingNodes == 0 && node.mBFSColor != mAnimatedGraphBFSColor) {
|
||||
node.mBFSColor = mAnimatedGraphBFSColor;
|
||||
updatedNodesCount++;
|
||||
|
@ -430,7 +434,7 @@ import javax.annotation.Nullable;
|
|||
}
|
||||
}
|
||||
for (int i = 0; i < mActiveAnimations.size(); i++) {
|
||||
AnimationDriver animation = mActiveAnimations.get(i);
|
||||
AnimationDriver animation = mActiveAnimations.valueAt(i);
|
||||
AnimatedNode valueNode = animation.mAnimatedValue;
|
||||
if (valueNode.mActiveIncomingNodes == 0 && valueNode.mBFSColor != mAnimatedGraphBFSColor) {
|
||||
valueNode.mBFSColor = mAnimatedGraphBFSColor;
|
||||
|
@ -480,19 +484,15 @@ import javax.annotation.Nullable;
|
|||
// finished, then resize `mActiveAnimations`.
|
||||
if (hasFinishedAnimations) {
|
||||
int dest = 0;
|
||||
for (int i = 0; i < mActiveAnimations.size(); i++) {
|
||||
AnimationDriver animation = mActiveAnimations.get(i);
|
||||
if (!animation.mHasFinished) {
|
||||
mActiveAnimations.set(dest++, animation);
|
||||
} else {
|
||||
for (int i = mActiveAnimations.size() - 1; i >= 0; i--) {
|
||||
AnimationDriver animation = mActiveAnimations.valueAt(i);
|
||||
if (animation.mHasFinished) {
|
||||
WritableMap endCallbackResponse = Arguments.createMap();
|
||||
endCallbackResponse.putBoolean("finished", true);
|
||||
animation.mEndCallback.invoke(endCallbackResponse);
|
||||
mActiveAnimations.removeAt(i);
|
||||
}
|
||||
}
|
||||
for (int i = mActiveAnimations.size() - 1; i >= dest; i--) {
|
||||
mActiveAnimations.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue