Fix children of AndroidView not being re-laid out after they call requestLayout

Summary:
In ReactNative, we are fully controlling layout of all the Views, not allowing Android to layout anything for us. This is done by making onLayout() of the top-level View in the hierarchy to be empty. This works fine because we explicitly call measure/layout for all the Views when they need to be re-measured or re-laid out. There is however one case where this doesn't happen automatically: some Android Views such as DrawerLayout or ActionBar have children that don't have shadow nodes associated with them (such as a title in ActionBar). This results in situations where children of AndroidView will call requestLayout but they will never get relaid out, because shadow hierarchy doesn't know about them. Example: ActionBar has a seTitle method that will internally call TextView.setTitle() and that TextView will call requestLayout because its size may have changed. However, that TextView will never be remeasured or relaid out.

This diff is fixing it by keeping track of everyone who called requestLayout. Then, at the end of the update loop we go over the list a manually remeasure and relayout those Views.

Not a huge fan of how this is implemented (there MUST be a better way) but this works with least efforts. I'll see if I can improve it later.

Reviewed By: ahmedre

Differential Revision: D2757485
This commit is contained in:
Denis Koroskin 2015-12-16 14:57:07 -08:00 committed by Ahmed El-Helw
parent ad65b2a9e1
commit 529390b87c
3 changed files with 52 additions and 1 deletions

View File

@ -21,6 +21,14 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
/* package */ final class FlatUIViewOperationQueue extends UIViewOperationQueue {
private final FlatNativeViewHierarchyManager mNativeViewHierarchyManager;
private final ProcessLayoutRequests mProcessLayoutRequests = new ProcessLayoutRequests();
private final class ProcessLayoutRequests implements UIOperation {
@Override
public void execute() {
FlatViewGroup.processLayoutRequests();
}
}
/**
* UIOperation that updates DrawCommands for a View defined by reactTag.
@ -199,6 +207,10 @@ import com.facebook.react.uimanager.UIViewOperationQueue;
enqueueUIOperation(new DropViews(viewsToDrop));
}
public void enqueueProcessLayoutRequests() {
enqueueUIOperation(mProcessLayoutRequests);
}
public DetachAllChildrenFromViews enqueueDetachAllChildrenFromViews() {
DetachAllChildrenFromViews op = new DetachAllChildrenFromViews();
enqueueUIOperation(op);

View File

@ -10,6 +10,7 @@
package com.facebook.react.flat;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import javax.annotation.Nullable;
@ -46,12 +47,15 @@ import com.facebook.react.uimanager.ReactCompoundView;
}
}
private static final ArrayList<FlatViewGroup> LAYOUT_REQUESTS = new ArrayList<>();
private @Nullable InvalidateCallback mInvalidateCallback;
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
private int mDrawChildIndex = 0;
private boolean mIsAttached = false;
private boolean mIsLayoutRequested = false;
/* package */ FlatViewGroup(Context context) {
super(context);
@ -62,6 +66,16 @@ import com.facebook.react.uimanager.ReactCompoundView;
super.detachAllViewsFromParent();
}
@Override
public void requestLayout() {
if (mIsLayoutRequested) {
return;
}
mIsLayoutRequested = true;
LAYOUT_REQUESTS.add(this);
}
@Override
public int reactTagForTouch(float touchX, float touchY) {
for (NodeRegion nodeRegion : mNodeRegions) {
@ -167,7 +181,7 @@ import com.facebook.react.uimanager.ReactCompoundView;
for (int viewToAdd : viewsToAdd) {
if (viewToAdd > 0) {
View view = ensureViewHasNoParent(viewResolver.getView(viewToAdd));
addView(view, -1, ensureLayoutParams(view.getLayoutParams()));
addViewInLayout(view, -1, ensureLayoutParams(view.getLayoutParams()), true);
} else {
View view = ensureViewHasNoParent(viewResolver.getView(-viewToAdd));
attachViewToParent(view, -1, ensureLayoutParams(view.getLayoutParams()));
@ -179,6 +193,29 @@ import com.facebook.react.uimanager.ReactCompoundView;
}
}
/* package */ void processLayoutRequest() {
mIsLayoutRequested = false;
for (int i = 0, childCount = getChildCount(); i != childCount; ++i) {
View child = getChildAt(i);
if (!child.isLayoutRequested()) {
continue;
}
child.measure(
MeasureSpec.makeMeasureSpec(child.getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(child.getHeight(), MeasureSpec.EXACTLY));
child.layout(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
}
}
/* package */ static void processLayoutRequests() {
for (int i = 0, numLayoutRequests = LAYOUT_REQUESTS.size(); i != numLayoutRequests; ++i) {
FlatViewGroup flatViewGroup = LAYOUT_REQUESTS.get(i);
flatViewGroup.processLayoutRequest();
}
LAYOUT_REQUESTS.clear();
}
private View ensureViewHasNoParent(View view) {
ViewParent oldParent = view.getParent();
if (oldParent != null) {

View File

@ -84,6 +84,8 @@ import com.facebook.react.uimanager.CatalystStylesDiffMap;
mOperationsQueue.enqueueDropViews(collectViewTags(mViewsToDrop));
mViewsToDrop.clear();
}
mOperationsQueue.enqueueProcessLayoutRequests();
}
/**