Skip to content

[Android] Redundant onLayout event #7202

Description

@hayeah

Sample App: https://rnplay.org/apps/L_-nNQ

I am using a RootView component to detect device rotation, but when animating an absolutely positioned child View in the class hierarchy, the RootView is getting redundant layout events.

<RootView onLayout={onDeviceRotation}>
   <Animated.View>
   </Animated.View>
</RootView>

On Android, onDeviceRotation gets a layout event for each frame (when the child view changes). On iOS, onDeviceRotation is called only on initial layout.

Digging into the source code, I found that:

  • On iOS, if a view is dirtied but if its frame doesn't change, then onLayout doesn't happen.
  • On Android, if a view is dirtied but if its frame doesn't change, onLayout DOES happen.

sample_app___react_native_playground

The relevant code is below.

iOS Side

RCTUIManager dispatches onLayout event for views that received new frames.

See: https://github.com/facebook/react-native/blob/02578df4f731b411b7b0be5f5dd83bd7233d2800/React/Modules/RCTUIManager.m#L550-L567

  for (RCTShadowView *shadowView in viewsWithNewFrames) {
    ...
    if (shadowView.onLayout) {
      CGRect frame = shadowView.frame;
      shadowView.onLayout(@{
        @"layout": @{
          @"x": @(frame.origin.x),
          @"y": @(frame.origin.y),
          @"width": @(frame.size.width),
          @"height": @(frame.size.height),
        },
      });
    }

RCTShadowNodes checks if frame changed, it not, don't add the view to viewsWithNewFrames, thus preventing onLayout event to get dispatched.

See: https://github.com/facebook/react-native/blob/02578df4f731b411b7b0be5f5dd83bd7233d2800/React/Views/RCTShadowView.m#L152-L155

if (!CGRectEqualToRect(frame, _frame)) {
  _frame = frame;
  [viewsWithNewFrame addObject:self];
}

Android Side

Android always dispatch an event if the layout is dirtied. In our case, the child view dirties the parent. We can do something similar as iOS, by checking if the frame changed before dispatching an event.

See: https://github.com/facebook/react-native/blob/02578df4f731b411b7b0be5f5dd83bd7233d2800/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java#L729-L744

cssNode.dispatchUpdates(
          absoluteX,
          absoluteY,
          mOperationsQueue,
          mNativeViewHierarchyOptimizer);

      // notify JS about layout event if requested
      if (cssNode.shouldNotifyOnLayout()) {
        eventDispatcher.dispatchEvent(
            OnLayoutEvent.obtain(
                tag,
                cssNode.getScreenX(),
                cssNode.getScreenY(),
                cssNode.getScreenWidth(),
                cssNode.getScreenHeight()));
      }

Specifically, we can change dispatchUpdates to return false if frame didn't change, and avoid dispatching the onLayout event.


  • Provide a minimal code snippet / rnplay example that reproduces the bug.
  • [x Provide screenshots where appropriate
  • What's the version of React Native you're using? 0.24.1
  • Does this occur on iOS, Android or both? Inconsistency between Android and iOS.
  • Are you using Mac, Linux or Windows? Mac

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions