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:
Ryan Gomba 2016-11-11 01:06:18 -08:00 committed by Facebook Github Bot
parent 617432c8c3
commit 6f5433febe
1 changed files with 22 additions and 22 deletions

View File

@ -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);
}
}
}
}