Permalink
Browse files

Add support for dynamically sized ReactRootView

Reviewed By: achen1, AaaChiuuu

Differential Revision: D5745093

fbshipit-source-id: 65d85252ab8a0ca38322f49a3d4812380d5228c4
  • Loading branch information...
mdvacca authored and facebook-github-bot committed Sep 9, 2017
1 parent 1afc93d commit 4ca617211b23754ffecf23c1ddc8772bc32420f0
@@ -27,6 +27,7 @@
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
@@ -40,6 +41,7 @@
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.MeasureSpecProvider;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.RootView;
import com.facebook.react.uimanager.SizeMonitoringFrameLayout;
@@ -60,7 +62,8 @@
* subsequent touch events related to that gesture (in case when JS code want to handle that
* gesture).
*/
public class ReactRootView extends SizeMonitoringFrameLayout implements RootView {
public class ReactRootView extends SizeMonitoringFrameLayout
implements RootView, MeasureSpecProvider {
/**
* Listener interface for react root view events
@@ -81,6 +84,9 @@
private boolean mIsAttachedToInstance;
private boolean mShouldLogContentAppeared;
private final JSTouchDispatcher mJSTouchDispatcher = new JSTouchDispatcher(this);
private boolean mWasMeasured = false;
private int mWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
private int mHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
public ReactRootView(Context context) {
super(context);
@@ -98,19 +104,72 @@ public ReactRootView(Context context, AttributeSet attrs, int defStyle) {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.onMeasure");
try {
setMeasuredDimension(
MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
mWidthMeasureSpec = widthMeasureSpec;
mHeightMeasureSpec = heightMeasureSpec;
int width = 0;
int height = 0;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childSize =
child.getLeft()
+ child.getMeasuredWidth()
+ child.getPaddingLeft()
+ child.getPaddingRight();
width = Math.max(width, childSize);
}
} else {
width = MeasureSpec.getSize(widthMeasureSpec);
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
int childSize =
child.getTop()
+ child.getMeasuredHeight()
+ child.getPaddingTop()
+ child.getPaddingBottom();
height = Math.max(height, childSize);
}
} else {
height = MeasureSpec.getSize(heightMeasureSpec);
}
setMeasuredDimension(width, height);
mWasMeasured = true;
// Check if we were waiting for onMeasure to attach the root view.
if (mReactInstanceManager != null && !mIsAttachedToInstance) {
attachToReactInstanceManager();
} else {
updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec);
}
enableLayoutCalculation();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
@Override
public int getWidthMeasureSpec() {
if (!mWasMeasured && getLayoutParams() != null && getLayoutParams().width > 0) {
return MeasureSpec.makeMeasureSpec(getLayoutParams().width, MeasureSpec.EXACTLY);
}
return mWidthMeasureSpec;
}
@Override
public int getHeightMeasureSpec() {
if (!mWasMeasured && getLayoutParams() != null && getLayoutParams().height > 0) {
return MeasureSpec.makeMeasureSpec(getLayoutParams().height, MeasureSpec.EXACTLY);
}
return mHeightMeasureSpec;
}
@Override
public void onChildStartedNativeGesture(MotionEvent androidEvent) {
if (mReactInstanceManager == null || !mIsAttachedToInstance ||
@@ -239,11 +298,51 @@ public void startReactApplication(
}
attachToReactInstanceManager();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
private void enableLayoutCalculation() {
if (mReactInstanceManager == null) {
FLog.w(
ReactConstants.TAG,
"Unable to enable layout calculation for uninitialized ReactInstanceManager");
return;
}
final ReactContext reactApplicationContext = mReactInstanceManager.getCurrentReactContext();
if (reactApplicationContext != null) {
reactApplicationContext
.getCatalystInstance()
.getNativeModule(UIManagerModule.class)
.getUIImplementation()
.enableLayoutCalculationForRootNode(getRootViewTag());
}
}
private void updateRootLayoutSpecs(final int widthMeasureSpec, final int heightMeasureSpec) {
if (mReactInstanceManager == null) {
FLog.w(
ReactConstants.TAG,
"Unable to update root layout specs for uninitialized ReactInstanceManager");
return;
}
final ReactContext reactApplicationContext = mReactInstanceManager.getCurrentReactContext();
if (reactApplicationContext != null) {
reactApplicationContext.runUIBackgroundRunnable(
new GuardedRunnable(reactApplicationContext) {
@Override
public void runGuarded() {
reactApplicationContext
.getCatalystInstance()
.getNativeModule(UIManagerModule.class)
.updateRootLayoutSpecs(getRootViewTag(), widthMeasureSpec, heightMeasureSpec);
}
});
}
}
/**
* Unmount the react application at this root view, reclaiming any JS memory associated with that
* application. If {@link #startReactApplication} is called, this method must be called before the
@@ -0,0 +1,17 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.uimanager;
import android.view.View;
/**
* Interface for a {@link View} subclass that provides the width and height measure specs from its
* measure pass. This is currently used to re-measure the root view by reusing the specs for yoga
* layout calculations.
*/
public interface MeasureSpecProvider {
int getWidthMeasureSpec();
int getHeightMeasureSpec();
}
@@ -9,9 +9,6 @@
package com.facebook.react.uimanager;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import android.content.res.Resources;
import android.util.Log;
import android.util.SparseArray;
@@ -22,7 +19,6 @@
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.PopupMenu;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.animation.Animation;
import com.facebook.react.animation.AnimationListener;
@@ -39,6 +35,8 @@
import com.facebook.react.uimanager.layoutanimation.LayoutAnimationListener;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Delegate of {@link UIManagerModule} that owns the native view hierarchy and mapping between
@@ -137,12 +135,7 @@ public synchronized void updateViewExtraData(int tag, Object extraData) {
}
public synchronized void updateLayout(
int parentTag,
int tag,
int x,
int y,
int width,
int height) {
int parentTag, int tag, int x, int y, int width, int height) {
UiThreadUtil.assertOnUiThread();
SystraceMessage.beginSection(
Systrace.TRACE_TAG_REACT_VIEW,
@@ -168,6 +161,19 @@ public synchronized void updateLayout(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
// We update the layout of the ReactRootView when there is a change in the layout of its child.
// This is required to re-measure the size of the native View container (usually a
// FrameLayout) that is configured with layout_height = WRAP_CONTENT or layout_width =
// WRAP_CONTENT
//
// This code is going to be executed ONLY when there is a change in the size of the Root
// View defined in the js side. Changes in the layout of inner views will not trigger an update
// on the layour of the Root View.
ViewParent parent = viewToUpdate.getParent();
if (parent instanceof RootView) {
parent.requestLayout();
}
// Check if the parent of the view has to layout the view, or the child has to lay itself out.
if (!mRootTags.get(parentTag)) {
ViewManager parentViewManager = mTagsToViewManagers.get(parentTag);
Oops, something went wrong.

0 comments on commit 4ca6172

Please sign in to comment.