Skip to content

Commit

Permalink
Top-down onLayout events (#39644)
Browse files Browse the repository at this point in the history
Summary:

This makes Android Paper/Classic renderer fire `onLayout` events top down, like in Fabric/new Architecture. This gives a much more sane model for using layout events to calculate bottom/right-edge insets.

I was under the impression that Paper in general was bottom-up, but it turns out that is only true for Android and Windows (iOS seems totally deterministic).

This is a behavior change, but to my knowledge was never hit during the Fabric migration, and any JS code already written for both Android and iOS cannot make assumptions here anyways.

Changelog:
[General][Changed] - Make layout events top-down on Android classic renderer

Reviewed By: mdvacca

Differential Revision: D49627996
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Oct 3, 2023
1 parent 113de6e commit 4034b3a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ public interface ReactShadowNode<T extends ReactShadowNode> {
*/
void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue);

/** @return true if layout (position or dimensions) changed, false otherwise. */
/* package */ boolean dispatchUpdatesWillChangeLayout(float absoluteX, float absoluteY);

/* package */ boolean dispatchUpdates(
/* package */ void dispatchUpdates(
float absoluteX,
float absoluteY,
UIViewOperationQueue uiViewOperationQueue,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,32 @@ public void onAfterUpdateTransaction() {
@Override
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {}

/** @return true if layout (position or dimensions) changed, false otherwise. */
@Override
public boolean dispatchUpdates(
public boolean dispatchUpdatesWillChangeLayout(float absoluteX, float absoluteY) {
if (!hasNewLayout()) {
return false;
}

float layoutX = getLayoutX();
float layoutY = getLayoutY();
int newAbsoluteLeft = Math.round(absoluteX + layoutX);
int newAbsoluteTop = Math.round(absoluteY + layoutY);
int newAbsoluteRight = Math.round(absoluteX + layoutX + getLayoutWidth());
int newAbsoluteBottom = Math.round(absoluteY + layoutY + getLayoutHeight());

int newScreenX = Math.round(layoutX);
int newScreenY = Math.round(layoutY);
int newScreenWidth = newAbsoluteRight - newAbsoluteLeft;
int newScreenHeight = newAbsoluteBottom - newAbsoluteTop;

return newScreenX != mScreenX
|| newScreenY != mScreenY
|| newScreenWidth != mScreenWidth
|| newScreenHeight != mScreenHeight;
}

@Override
public void dispatchUpdates(
float absoluteX,
float absoluteY,
UIViewOperationQueue uiViewOperationQueue,
Expand Down Expand Up @@ -386,10 +409,6 @@ public boolean dispatchUpdates(
getScreenHeight());
}
}

return layoutHasChanged;
} else {
return false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
import com.facebook.systrace.SystraceMessage;
import com.facebook.yoga.YogaConstants;
import com.facebook.yoga.YogaDirection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
Expand Down Expand Up @@ -665,7 +667,20 @@ protected void updateViewHierarchy() {
.arg("rootTag", cssRoot.getReactTag())
.flush();
try {
applyUpdatesRecursive(cssRoot, 0f, 0f);
List<ReactShadowNode> onLayoutNodes = new ArrayList<>();
applyUpdatesRecursive(cssRoot, 0f, 0f, onLayoutNodes);

for (ReactShadowNode node : onLayoutNodes) {
mEventDispatcher.dispatchEvent(
OnLayoutEvent.obtain(
-1, /* surfaceId not used in classic renderer */
node.getReactTag(),
node.getScreenX(),
node.getScreenY(),
node.getScreenWidth(),
node.getScreenHeight()));
}

} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
Expand Down Expand Up @@ -951,39 +966,34 @@ protected void calculateRootLayout(ReactShadowNode cssRoot) {
}
}

protected void applyUpdatesRecursive(ReactShadowNode cssNode, float absoluteX, float absoluteY) {
protected void applyUpdatesRecursive(
ReactShadowNode cssNode,
float absoluteX,
float absoluteY,
List<ReactShadowNode> onLayoutNodes) {
if (!cssNode.hasUpdates()) {
return;
}

if (cssNode.dispatchUpdatesWillChangeLayout(absoluteX, absoluteY)
&& cssNode.shouldNotifyOnLayout()
&& !mShadowNodeRegistry.isRootNode(cssNode.getReactTag())) {
onLayoutNodes.add(cssNode);
}

Iterable<? extends ReactShadowNode> cssChildren = cssNode.calculateLayoutOnChildren();
if (cssChildren != null) {
for (ReactShadowNode cssChild : cssChildren) {
applyUpdatesRecursive(
cssChild, absoluteX + cssNode.getLayoutX(), absoluteY + cssNode.getLayoutY());
cssChild,
absoluteX + cssNode.getLayoutX(),
absoluteY + cssNode.getLayoutY(),
onLayoutNodes);
}
}

int tag = cssNode.getReactTag();
if (!mShadowNodeRegistry.isRootNode(tag)) {
boolean frameDidChange =
cssNode.dispatchUpdates(
absoluteX, absoluteY, mOperationsQueue, mNativeViewHierarchyOptimizer);

// Notify JS about layout event if requested
// and if the position or dimensions actually changed
// (consistent with iOS).
if (frameDidChange && cssNode.shouldNotifyOnLayout()) {
mEventDispatcher.dispatchEvent(
OnLayoutEvent.obtain(
-1, /* surfaceId not used in classic renderer */
tag,
cssNode.getScreenX(),
cssNode.getScreenY(),
cssNode.getScreenWidth(),
cssNode.getScreenHeight()));
}
}
cssNode.dispatchUpdates(absoluteX, absoluteY, mOperationsQueue, mNativeViewHierarchyOptimizer);

cssNode.markUpdateSeen();
mNativeViewHierarchyOptimizer.onViewUpdatesCompleted(cssNode);
}
Expand Down

0 comments on commit 4034b3a

Please sign in to comment.