From 565a7439ac8af66ab9b15e28119388608fd287c5 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Tue, 1 Nov 2022 12:50:05 -0700 Subject: [PATCH] Refactor EventDriverAnimations to customize event match Summary: Changelog: [Internal] - Refactor match logic on determining whether to run an EventAnimationDriver (drivers for natively animated events) for an Event dispatched. Previously, drivers were stored by key on the NativeAnimatedNodesManager (based on event handler and viewTag) and has been refactored to be stored in a list for easier matching. This diff changes it so the match logic for running an EventAnimationDriver happens on the Event instance. This change is motivated by PointerEvents needing custom match logic (done on a following change). Reviewed By: javache Differential Revision: D40691002 fbshipit-source-id: e4f6742a2af3b751214aefa1fc069f65e8e71d77 --- .../react/animated/EventAnimationDriver.java | 7 +- .../animated/NativeAnimatedNodesManager.java | 81 ++++++++++--------- .../react/uimanager/events/Event.java | 18 +++++ 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.java b/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.java index fce99e38ba4..edc92c238c3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/EventAnimationDriver.java @@ -21,8 +21,13 @@ import java.util.List; /* package */ class EventAnimationDriver implements RCTEventEmitter { private List mEventPath; /* package */ ValueAnimatedNode mValueNode; + /* package */ String mEventName; + /* package */ int mViewTag; - public EventAnimationDriver(List eventPath, ValueAnimatedNode valueNode) { + public EventAnimationDriver( + String eventName, int viewTag, List eventPath, ValueAnimatedNode valueNode) { + mEventName = eventName; + mViewTag = viewTag; mEventPath = eventPath; mValueNode = valueNode; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java index 9a6953529b4..bb626bed0b2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedNodesManager.java @@ -31,11 +31,9 @@ import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.EventDispatcherListener; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; -import java.util.Map; import java.util.Queue; /** @@ -58,9 +56,9 @@ public class NativeAnimatedNodesManager implements EventDispatcherListener { private final SparseArray mAnimatedNodes = new SparseArray<>(); private final SparseArray mActiveAnimations = new SparseArray<>(); private final SparseArray mUpdatedNodes = new SparseArray<>(); - // Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time - // there will be only one driver per mapping so all code code should be optimized around that. - private final Map> mEventDrivers = new HashMap<>(); + // List of event animation drivers for an event on view. + // There may be multiple drivers for the same event and view. + private final List mEventDrivers = new ArrayList<>(); private final ReactApplicationContext mReactApplicationContext; private int mAnimatedGraphBFSColor = 0; // Used to avoid allocating a new array on every frame in `runUpdates` and `onEventDispatch`. @@ -501,7 +499,8 @@ public class NativeAnimatedNodesManager implements EventDispatcherListener { } @UiThread - public void addAnimatedEventToView(int viewTag, String eventName, ReadableMap eventMapping) { + public void addAnimatedEventToView( + int viewTag, String eventHandlerName, ReadableMap eventMapping) { int nodeTag = eventMapping.getInt("animatedValueTag"); AnimatedNode node = mAnimatedNodes.get(nodeTag); if (node == null) { @@ -512,8 +511,8 @@ public class NativeAnimatedNodesManager implements EventDispatcherListener { throw new JSApplicationIllegalArgumentException( "addAnimatedEventToView: Animated node on view [" + viewTag - + "] connected to event (" - + eventName + + "] connected to event handler (" + + eventHandlerName + ") should be of type " + ValueAnimatedNode.class.getName()); } @@ -524,32 +523,27 @@ public class NativeAnimatedNodesManager implements EventDispatcherListener { pathList.add(path.getString(i)); } - EventAnimationDriver event = new EventAnimationDriver(pathList, (ValueAnimatedNode) node); - String key = viewTag + eventName; - if (mEventDrivers.containsKey(key)) { - mEventDrivers.get(key).add(event); - } else { - List drivers = new ArrayList<>(1); - drivers.add(event); - mEventDrivers.put(key, drivers); - } + String eventName = normalizeEventName(eventHandlerName); + + EventAnimationDriver eventDriver = + new EventAnimationDriver(eventName, viewTag, pathList, (ValueAnimatedNode) node); + mEventDrivers.add(eventDriver); } @UiThread - public void removeAnimatedEventFromView(int viewTag, String eventName, int animatedValueTag) { - String key = viewTag + eventName; - if (mEventDrivers.containsKey(key)) { - List driversForKey = mEventDrivers.get(key); - if (driversForKey.size() == 1) { - mEventDrivers.remove(viewTag + eventName); - } else { - ListIterator it = driversForKey.listIterator(); - while (it.hasNext()) { - if (it.next().mValueNode.mTag == animatedValueTag) { - it.remove(); - break; - } - } + public void removeAnimatedEventFromView( + int viewTag, String eventHandlerName, int animatedValueTag) { + + String eventName = normalizeEventName(eventHandlerName); + + ListIterator it = mEventDrivers.listIterator(); + while (it.hasNext()) { + EventAnimationDriver driver = it.next(); + if (eventName.equals(driver.mEventName) + && viewTag == driver.mViewTag + && animatedValueTag == driver.mValueNode.mTag) { + it.remove(); + break; } } } @@ -585,18 +579,19 @@ public class NativeAnimatedNodesManager implements EventDispatcherListener { if (uiManager == null) { return; } - String eventName = uiManager.resolveCustomDirectEventName(event.getEventName()); - if (eventName == null) { - eventName = ""; - } - List driversForKey = mEventDrivers.get(event.getViewTag() + eventName); - if (driversForKey != null) { - for (EventAnimationDriver driver : driversForKey) { + boolean foundAtLeastOneDriver = false; + Event.EventAnimationDriverMatchSpec matchSpec = event.getEventAnimationDriverMatchSpec(); + for (EventAnimationDriver driver : mEventDrivers) { + if (matchSpec.match(driver.mViewTag, driver.mEventName)) { + foundAtLeastOneDriver = true; stopAnimationsForNode(driver.mValueNode); event.dispatch(driver); mRunUpdateNodeList.add(driver.mValueNode); } + } + + if (foundAtLeastOneDriver) { updateNodes(mRunUpdateNodeList); mRunUpdateNodeList.clear(); } @@ -824,4 +819,14 @@ public class NativeAnimatedNodesManager implements EventDispatcherListener { mWarnedAboutGraphTraversal = false; } } + + private String normalizeEventName(String eventHandlerName) { + // Fabric UIManager also makes this assumption + String eventName = eventHandlerName; + if (eventHandlerName.startsWith("on")) { + eventName = "top" + eventHandlerName.substring(2); + } + + return eventName; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java index 0b6a1140c8e..01b355def8b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/Event.java @@ -40,6 +40,7 @@ public abstract class Event { private int mViewTag; private long mTimestampMs; private int mUniqueID = sUniqueID++; + private @Nullable EventAnimationDriverMatchSpec mEventAnimationDriverMatchSpec; protected Event() {} @@ -165,6 +166,19 @@ public abstract class Event { /** @return the name of this event as registered in JS */ public abstract String getEventName(); + public EventAnimationDriverMatchSpec getEventAnimationDriverMatchSpec() { + if (mEventAnimationDriverMatchSpec == null) { + mEventAnimationDriverMatchSpec = + new EventAnimationDriverMatchSpec() { + @Override + public boolean match(int viewTag, String eventName) { + return viewTag == getViewTag() && eventName.equals(getEventName()); + }; + }; + } + return mEventAnimationDriverMatchSpec; + } + /** * Dispatch this event to JS using the given event emitter. Compatible with old and new renderer. * Instead of using this or dispatchModern, it is recommended that you simply override @@ -225,4 +239,8 @@ public abstract class Event { } dispatch(rctEventEmitter); } + + public interface EventAnimationDriverMatchSpec { + boolean match(int viewTag, String eventName); + } }