Skip to content
Closed
1 change: 1 addition & 0 deletions Examples/UIExplorer/js/UIExplorerExampleList.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ const styles = StyleSheet.create({
padding: 5,
fontWeight: '500',
fontSize: 11,
backgroundColor: '#eeeeee',
},
row: {
backgroundColor: 'white',
Expand Down
4 changes: 2 additions & 2 deletions Libraries/Components/ScrollView/ScrollView.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,6 @@ const ScrollView = React.createClass({
* `stickyHeaderIndices={[0]}` will cause the first child to be fixed to the
* top of the scroll view. This property is not supported in conjunction
* with `horizontal={true}`.
* @platform ios
*/
stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number),
style: StyleSheetPropType(ViewStylePropTypes),
Expand Down Expand Up @@ -453,7 +452,8 @@ const ScrollView = React.createClass({
ref={this._setInnerViewRef}
style={contentContainerStyle}
removeClippedSubviews={this.props.removeClippedSubviews}
collapsable={false}>
collapsable={false}
collapseChildren={!this.props.stickyHeaderIndices}>
{this.props.children}
</View>;

Expand Down
9 changes: 9 additions & 0 deletions Libraries/Components/View/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,15 @@ const View = React.createClass({
*/
collapsable: PropTypes.bool,

/**
* Same as `collapsable` but also applies to all of this view's children.
* Setting this to `false` ensures that the all children exists in the native
* view hierarchy.
*
* @platform android
*/
collapseChildren: PropTypes.bool,

/**
* Whether this `View` needs to rendered offscreen and composited with an alpha
* in order to preserve 100% correct colors and blending behavior. The default
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;

import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.annotations.ReactProp;
Expand Down Expand Up @@ -56,6 +56,8 @@ public void setTransform(T view, ReadableArray matrix) {
} else {
setTransformProperty(view, matrix);
}

updateClipping(view);
}

@ReactProp(name = PROP_OPACITY, defaultFloat = 1.f)
Expand Down Expand Up @@ -114,30 +116,40 @@ public void setImportantForAccessibility(T view, String importantForAccessibilit
@ReactProp(name = PROP_ROTATION)
public void setRotation(T view, float rotation) {
view.setRotation(rotation);

updateClipping(view);
}

@Deprecated
@ReactProp(name = PROP_SCALE_X, defaultFloat = 1f)
public void setScaleX(T view, float scaleX) {
view.setScaleX(scaleX);

updateClipping(view);
}

@Deprecated
@ReactProp(name = PROP_SCALE_Y, defaultFloat = 1f)
public void setScaleY(T view, float scaleY) {
view.setScaleY(scaleY);

updateClipping(view);
}

@Deprecated
@ReactProp(name = PROP_TRANSLATE_X, defaultFloat = 0f)
public void setTranslateX(T view, float translateX) {
view.setTranslationX(PixelUtil.toPixelFromDIP(translateX));

updateClipping(view);
}

@Deprecated
@ReactProp(name = PROP_TRANSLATE_Y, defaultFloat = 0f)
public void setTranslateY(T view, float translateY) {
view.setTranslationY(PixelUtil.toPixelFromDIP(translateY));

updateClipping(view);
}

@ReactProp(name = PROP_ACCESSIBILITY_LIVE_REGION)
Expand Down Expand Up @@ -176,4 +188,11 @@ private static void resetTransformProperty(View view) {
view.setScaleX(1);
view.setScaleY(1);
}

private static void updateClipping(View view) {
ViewParent parent = view.getParent();
if (parent instanceof ReactClippingViewGroup) {
((ReactClippingViewGroup) parent).updateClippingRect();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

package com.facebook.react.uimanager;

public interface DrawingOrderViewGroup {
/**
* Returns if the ViewGroup implements custom drawing order.
*/
boolean isDrawingOrderEnabled();

/**
* Returns which child to draw for the specified index.
*/
int getDrawingOrder(int i);
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@ public void handleCreateView(
return;
}

node.setShouldCollapseChildren(shouldCollapseChildren(initialProps));

boolean isLayoutOnly = node.getViewClass().equals(ViewProps.VIEW_CLASS_NAME) &&
isLayoutOnlyAndCollapsable(initialProps);
isLayoutOnlyAndCollapsable(initialProps) &&
(node.getParent() == null || node.getParent().shouldCollapseChildren());
node.setIsLayoutOnly(isLayoutOnly);

if (!isLayoutOnly) {
Expand Down Expand Up @@ -435,7 +438,19 @@ private void transitionLayoutOnlyViewToNativeView(
mTagsWithLayoutVisited.clear();
}

private static boolean isLayoutOnlyAndCollapsable(@Nullable ReactStylesDiffMap props) {
private static boolean shouldCollapseChildren(@Nullable ReactStylesDiffMap props) {
if (props == null) {
return true;
}

if (props.hasKey(ViewProps.COLLAPSE_CHILDREN) && !props.getBoolean(ViewProps.COLLAPSE_CHILDREN, true)) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I was going to comment on this, If I don't do this native view counts doesn't match sticky indices. I think it is because of null react elements. Any other idea other than doing this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we're setting collapsable=false on all those children though -- I don't understand why that wouldn't be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some children are null so we can't set collapsable = false on them. I had to add a null check in children map here 1e87a60#diff-f8f8b220fc0d1573e4f8b78c84415075R455

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Then we should either have JS send the tags of the children that are sticky headers which the native view could then convert into indices (which would also fix the whole collapsable problem), or less ideally remap the indices in JS.

Copy link
Contributor Author

@janicduplessis janicduplessis Aug 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you go about getting the sticky headers view tags in JS? Seems like it would be relatively complex since we can only get them after the view has been rendered using refs. Maybe remapping indexes to offset for null views that will be removed would end up a lot simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok this is actually harder than I thought because just adding collapsable=false doesn't really work because children are not actually View components so adding the collapsable prop doesn't do anything. In the case of ListView it's always a StaticRenderer which ignores the collapsable prop. I'm kind of leaning back towards adding the collapsableChildren prop to View that ensures that all it's children map to a native View and don't get optimized out.

Here's a simple example to show why adding the collapsable props to ScrollView children in JS doesn't work:

const CustomView = ({ text }) => <View><Text>{text}</Text></View>;

<ScrollView>
   // Works, view gets rendered with `collapsable=false`
  <View />
  // Doesn't work because the collapsable prop gets passed to `CustomView` but is not 
  // passed down to it's `View` child
  <CustomView text="hello" />
</ScrollView>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I understand the problems you're having. Ideally we'd have some way to refer to the child views other than by index (since it isn't stable), but that doesn't exist right now and I don't know a good way to do it. I'm fine doing collapsableChildren false in this case then. (Actually, another option would be a create a custom StickyHeader native view class that ScrollView knows about and can do instanceof checks in native... but that would require a change in API)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with collapsableChildren, this will allow to keep the same API and could even be useful in the future for other cases where we want an index based API like stickyHeaderIndices.

}

return true;
}

private static boolean isCollapsable(@Nullable ReactStylesDiffMap props) {
if (props == null) {
return true;
}
Expand All @@ -444,6 +459,18 @@ private static boolean isLayoutOnlyAndCollapsable(@Nullable ReactStylesDiffMap p
return false;
}

return true;
}

private static boolean isLayoutOnlyAndCollapsable(@Nullable ReactStylesDiffMap props) {
if (props == null) {
return true;
}

if (!isCollapsable(props) || !shouldCollapseChildren(props)) {
return false;
}

ReadableMapKeySetIterator keyIterator = props.mBackingMap.keySetIterator();
while (keyIterator.hasNextKey()) {
if (!ViewProps.isLayoutOnly(keyIterator.nextKey())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
package com.facebook.react.uimanager;

/**
* This interface should be implemented be native ViewGroup subclasses that can represent more
* This interface should be implemented by native ViewGroup subclasses that can represent more
* than a single react node. In that case, virtual and non-virtual (mapping to a View) elements
* can overlap, and TouchTargetHelper may incorrectly dispatch touch event to a wrong element
* because it priorities children over parents.
*/
public interface ReactCompoundViewGroup extends ReactCompoundView {
/**
* Returns true if react node responsible for the touch even is flattened into this ViewGroup.
* Returns true if react node responsible for the touch event is flattened into this ViewGroup.
* Use reactTagForTouch() to get its tag.
*/
boolean interceptsTouchEvent(float touchX, float touchY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class ReactShadowNode extends CSSNode {
private boolean mNodeUpdated = true;

// layout-only nodes
private boolean mShouldCollapseChildren;
private boolean mIsLayoutOnly;
private int mTotalNativeChildren = 0;
private @Nullable ReactShadowNode mNativeParent;
Expand Down Expand Up @@ -367,6 +368,14 @@ public boolean isLayoutOnly() {
return mIsLayoutOnly;
}

public void setShouldCollapseChildren(boolean collapsable) {
mShouldCollapseChildren = collapsable;
}

public boolean shouldCollapseChildren() {
return mShouldCollapseChildren;
}

public int getTotalNativeChildren() {
return mTotalNativeChildren;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,12 @@ private static View findClosestReactAncestor(View view) {
*/
private static View findTouchTargetView(float[] eventCoords, ViewGroup viewGroup) {
int childrenCount = viewGroup.getChildCount();
final boolean useCustomOrder = (viewGroup instanceof DrawingOrderViewGroup) &&
((DrawingOrderViewGroup) viewGroup).isDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
View child = viewGroup.getChildAt(i);
int childIndex = useCustomOrder ?
((DrawingOrderViewGroup) viewGroup).getDrawingOrder(i) : i;
View child = viewGroup.getChildAt(childIndex);
PointF childPoint = mTempPoint;
if (isTransformedTouchPointInView(eventCoords[0], eventCoords[1], viewGroup, child, childPoint)) {
// If it is contained within the child View, the childPoint value will contain the view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -823,4 +823,8 @@ public int resolveRootTagFromReactTag(int reactTag) {

return rootTag;
}

public ViewManager getViewManager(String name) {
return mViewManagers.get(name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class ViewProps {
public static final String ALIGN_SELF = "alignSelf";
public static final String BOTTOM = "bottom";
public static final String COLLAPSABLE = "collapsable";
public static final String COLLAPSE_CHILDREN = "collapseChildren";
public static final String FLEX = "flex";
public static final String FLEX_GROW = "flexGrow";
public static final String FLEX_SHRINK = "flexShrink";
Expand Down
Loading