Implement ScrollView sticky headers on Android
Summary: This adds support for sticky headers on Android. The implementation if based primarily on the iOS one (https://github.com/facebook/react-native/blob/master/React/Views/RCTScrollView.m#L272) and adds some stuff that was missing to be able to handle z-index, view clipping, view hierarchy optimization and touch handling properly. Some notable changes: - Add `ChildDrawingOrderDelegate` interface to allow changing the `ViewGroup` drawing order using `ViewGroup#getChildDrawingOrder`. This is used to change the content view drawing order to make sure headers are drawn over the other cells. Right now I'm only reversing the drawing order as drawing only the header views last added a lot of complexity especially because of view clipping and I don't think it should cause issues. - Add `collapsableChildren` prop that works like `collapsable` but applies to every child of the view. This is needed to be able to reference sticky headers by their indices otherwise some subviews can get optimized out and break indexes. Closes https://github.com/facebook/react-native/pull/9456 Differential Revision: D3827366 fbshipit-source-id: cab044cfdbe2ccb98e1ecd3e02ed3ceaa253eb78
This commit is contained in:
parent
272d3ded4f
commit
0e8b75b22c
|
@ -185,6 +185,7 @@ const styles = StyleSheet.create({
|
|||
padding: 5,
|
||||
fontWeight: '500',
|
||||
fontSize: 11,
|
||||
backgroundColor: '#eeeeee',
|
||||
},
|
||||
row: {
|
||||
backgroundColor: 'white',
|
||||
|
|
|
@ -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),
|
||||
|
@ -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>;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,7 +5,7 @@ package com.facebook.react.uimanager;
|
|||
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;
|
||||
|
@ -56,6 +56,8 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
|||
} else {
|
||||
setTransformProperty(view, matrix);
|
||||
}
|
||||
|
||||
updateClipping(view);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_OPACITY, defaultFloat = 1.f)
|
||||
|
@ -114,30 +116,40 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
|||
@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)
|
||||
|
@ -176,4 +188,11 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
|||
view.setScaleX(1);
|
||||
view.setScaleY(1);
|
||||
}
|
||||
|
||||
private static void updateClipping(View view) {
|
||||
ViewParent parent = view.getParent();
|
||||
if (parent instanceof ReactClippingViewGroup) {
|
||||
((ReactClippingViewGroup) parent).updateClippingRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -92,8 +92,11 @@ public class NativeViewHierarchyOptimizer {
|
|||
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) {
|
||||
|
@ -435,7 +438,19 @@ public class NativeViewHierarchyOptimizer {
|
|||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isCollapsable(@Nullable ReactStylesDiffMap props) {
|
||||
if (props == null) {
|
||||
return true;
|
||||
}
|
||||
|
@ -444,6 +459,18 @@ public class NativeViewHierarchyOptimizer {
|
|||
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())) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
@ -367,6 +368,14 @@ public class ReactShadowNode extends CSSNode {
|
|||
return mIsLayoutOnly;
|
||||
}
|
||||
|
||||
public void setShouldCollapseChildren(boolean collapsable) {
|
||||
mShouldCollapseChildren = collapsable;
|
||||
}
|
||||
|
||||
public boolean shouldCollapseChildren() {
|
||||
return mShouldCollapseChildren;
|
||||
}
|
||||
|
||||
public int getTotalNativeChildren() {
|
||||
return mTotalNativeChildren;
|
||||
}
|
||||
|
|
|
@ -124,8 +124,12 @@ public class TouchTargetHelper {
|
|||
*/
|
||||
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
|
||||
|
|
|
@ -823,4 +823,8 @@ public class UIImplementation {
|
|||
|
||||
return rootTag;
|
||||
}
|
||||
|
||||
public ViewManager getViewManager(String name) {
|
||||
return mViewManagers.get(name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -9,10 +9,6 @@
|
|||
|
||||
package com.facebook.react.views.scroll;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
|
@ -24,13 +20,21 @@ import android.view.View;
|
|||
import android.widget.OverScroller;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.common.ReactConstants;
|
||||
import com.facebook.react.uimanager.MeasureSpecAssertions;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.facebook.react.uimanager.events.NativeGestureUtil;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroup;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.views.view.ReactViewGroup;
|
||||
import com.facebook.react.views.view.ReactViewManager;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A simple subclass of ScrollView that doesn't dispatch measure and layout to its children and has
|
||||
|
@ -58,6 +62,16 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
|||
private @Nullable String mScrollPerfTag;
|
||||
private @Nullable Drawable mEndBackground;
|
||||
private int mEndFillColor = Color.TRANSPARENT;
|
||||
private @Nullable int[] mStickyHeaderIndices;
|
||||
private ReactViewManager mViewManager;
|
||||
|
||||
private final ReactViewGroup.ChildDrawingOrderDelegate mContentDrawingOrderDelegate =
|
||||
new ReactViewGroup.ChildDrawingOrderDelegate() {
|
||||
@Override
|
||||
public int getChildDrawingOrder(ReactViewGroup viewGroup, int drawingIndex) {
|
||||
return viewGroup.getChildCount() - drawingIndex - 1;
|
||||
}
|
||||
};
|
||||
|
||||
public ReactScrollView(ReactContext context) {
|
||||
this(context, null);
|
||||
|
@ -65,6 +79,10 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
|||
|
||||
public ReactScrollView(ReactContext context, @Nullable FpsListener fpsListener) {
|
||||
super(context);
|
||||
|
||||
UIManagerModule uiManager = context.getNativeModule(UIManagerModule.class);
|
||||
mViewManager = (ReactViewManager) uiManager.getUIImplementation().getViewManager(ReactViewManager.REACT_CLASS);
|
||||
|
||||
mFpsListener = fpsListener;
|
||||
|
||||
if (!sTriedToGetScrollerField) {
|
||||
|
@ -139,6 +157,7 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
|||
super.onScrollChanged(x, y, oldX, oldY);
|
||||
|
||||
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
|
||||
dockClosestSectionHeader();
|
||||
if (mRemoveClippedSubviews) {
|
||||
updateClippingRect();
|
||||
}
|
||||
|
@ -309,6 +328,84 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
|
|||
}
|
||||
}
|
||||
|
||||
public void setStickyHeaderIndices(@Nullable ReadableArray indices) {
|
||||
if (indices == null) {
|
||||
mStickyHeaderIndices = null;
|
||||
} else {
|
||||
int[] indicesArray = new int[indices.size()];
|
||||
for (int i = 0; i < indices.size(); i++) {
|
||||
indicesArray[i] = indices.getInt(i);
|
||||
}
|
||||
|
||||
mStickyHeaderIndices = indicesArray;
|
||||
}
|
||||
dockClosestSectionHeader();
|
||||
}
|
||||
|
||||
private void dockClosestSectionHeader() {
|
||||
if (mStickyHeaderIndices == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
View previousHeader = null;
|
||||
View currentHeader = null;
|
||||
View nextHeader = null;
|
||||
ReactViewGroup contentView = (ReactViewGroup) getChildAt(0);
|
||||
if (contentView == null) {
|
||||
return;
|
||||
}
|
||||
contentView.setChildDrawingOrderDelegate(mContentDrawingOrderDelegate);
|
||||
|
||||
int scrollY = getScrollY();
|
||||
for (int idx : mStickyHeaderIndices) {
|
||||
// If the subviews are out of sync with the sticky header indices don't
|
||||
// do anything.
|
||||
if (idx >= mViewManager.getChildCount(contentView)) {
|
||||
break;
|
||||
}
|
||||
|
||||
View header = mViewManager.getChildAt(contentView, idx);
|
||||
|
||||
// If nextHeader not yet found, search for docked headers.
|
||||
if (nextHeader == null) {
|
||||
int top = header.getTop();
|
||||
if (top > scrollY) {
|
||||
nextHeader = header;
|
||||
} else {
|
||||
previousHeader = currentHeader;
|
||||
currentHeader = header;
|
||||
}
|
||||
}
|
||||
|
||||
header.setTranslationY(0);
|
||||
}
|
||||
|
||||
if (currentHeader == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int currentHeaderTop = currentHeader.getTop();
|
||||
int currentHeaderHeight = currentHeader.getHeight();
|
||||
int yOffset = scrollY - currentHeaderTop;
|
||||
|
||||
if (nextHeader != null) {
|
||||
// The next header nudges the current header out of the way when it reaches
|
||||
// the top of the screen.
|
||||
int nextHeaderTop = nextHeader.getTop();
|
||||
int overlap = currentHeaderHeight - (nextHeaderTop - scrollY);
|
||||
yOffset -= Math.max(0, overlap);
|
||||
}
|
||||
|
||||
currentHeader.setTranslationY(yOffset);
|
||||
|
||||
if (previousHeader != null) {
|
||||
// The previous header sits right above the currentHeader's initial position
|
||||
// so it scrolls away nicely once the currentHeader has locked into place.
|
||||
yOffset = currentHeaderTop - previousHeader.getTop() - previousHeader.getHeight();
|
||||
previousHeader.setTranslationY(yOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
|
||||
if (mScroller != null) {
|
||||
|
|
|
@ -9,18 +9,18 @@
|
|||
|
||||
package com.facebook.react.views.scroll;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* View manager for {@link ReactScrollView} components.
|
||||
|
@ -104,6 +104,11 @@ public class ReactScrollViewManager
|
|||
view.setEndFillColor(color);
|
||||
}
|
||||
|
||||
@ReactProp(name = "stickyHeaderIndices")
|
||||
public void setStickyHeaderIndices(ReactScrollView view, @Nullable ReadableArray indices) {
|
||||
view.setStickyHeaderIndices(indices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Map<String, Integer> getCommandsMap() {
|
||||
return ReactScrollViewCommandHelper.getCommandsMap();
|
||||
|
|
|
@ -13,7 +13,9 @@ import javax.annotation.Nullable;
|
|||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.view.animation.Animation;
|
||||
|
@ -34,13 +36,17 @@ import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
|
|||
* initializes most of the storage needed for them.
|
||||
*/
|
||||
public class ReactViewGroup extends ViewGroup implements
|
||||
ReactInterceptingViewGroup, com.facebook.react.uimanager.ReactClippingViewGroup, ReactPointerEventsView, ReactHitSlopView {
|
||||
ReactInterceptingViewGroup, ReactClippingViewGroup, ReactPointerEventsView, ReactHitSlopView, DrawingOrderViewGroup {
|
||||
|
||||
private static final int ARRAY_CAPACITY_INCREMENT = 12;
|
||||
private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
|
||||
private static final LayoutParams sDefaultLayoutParam = new ViewGroup.LayoutParams(0, 0);
|
||||
/* should only be used in {@link #updateClippingToRect} */
|
||||
private static final Rect sHelperRect = new Rect();
|
||||
private static final RectF sHelperRect = new RectF();
|
||||
|
||||
public interface ChildDrawingOrderDelegate {
|
||||
int getChildDrawingOrder(ReactViewGroup view, int i);
|
||||
}
|
||||
|
||||
/**
|
||||
* This listener will be set for child views when removeClippedSubview property is enabled. When
|
||||
|
@ -93,6 +99,7 @@ public class ReactViewGroup extends ViewGroup implements
|
|||
private @Nullable ReactViewBackgroundDrawable mReactBackgroundDrawable;
|
||||
private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener;
|
||||
private boolean mNeedsOffscreenAlphaCompositing = false;
|
||||
private @Nullable ChildDrawingOrderDelegate mChildDrawingOrderDelegate;
|
||||
|
||||
public ReactViewGroup(Context context) {
|
||||
super(context);
|
||||
|
@ -290,8 +297,15 @@ public class ReactViewGroup extends ViewGroup implements
|
|||
private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFar) {
|
||||
View child = Assertions.assertNotNull(mAllChildren)[idx];
|
||||
sHelperRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
|
||||
boolean intersects = clippingRect
|
||||
.intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom);
|
||||
Matrix matrix = child.getMatrix();
|
||||
if (!matrix.isIdentity()) {
|
||||
matrix.mapRect(sHelperRect);
|
||||
}
|
||||
boolean intersects = clippingRect.intersects(
|
||||
(int) sHelperRect.left,
|
||||
(int) sHelperRect.top,
|
||||
(int) Math.ceil(sHelperRect.right),
|
||||
(int) Math.ceil(sHelperRect.bottom));
|
||||
boolean needUpdateClippingRecursive = false;
|
||||
// We never want to clip children that are being animated, as this can easily break layout :
|
||||
// when layout animation changes size and/or position of views contained inside a listview that
|
||||
|
@ -337,8 +351,15 @@ public class ReactViewGroup extends ViewGroup implements
|
|||
|
||||
// do fast check whether intersect state changed
|
||||
sHelperRect.set(subview.getLeft(), subview.getTop(), subview.getRight(), subview.getBottom());
|
||||
boolean intersects = mClippingRect
|
||||
.intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom);
|
||||
Matrix matrix = subview.getMatrix();
|
||||
if (!matrix.isIdentity()) {
|
||||
matrix.mapRect(sHelperRect);
|
||||
}
|
||||
boolean intersects = mClippingRect.intersects(
|
||||
(int) sHelperRect.left,
|
||||
(int) sHelperRect.top,
|
||||
(int) Math.ceil(sHelperRect.right),
|
||||
(int) Math.ceil(sHelperRect.bottom));
|
||||
|
||||
// If it was intersecting before, should be attached to the parent
|
||||
boolean oldIntersects = (subview.getParent() != null);
|
||||
|
@ -531,4 +552,27 @@ public class ReactViewGroup extends ViewGroup implements
|
|||
mHitSlopRect = rect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDrawingOrder(int i) {
|
||||
return getChildDrawingOrder(getChildCount(), i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDrawingOrderEnabled() {
|
||||
return isChildrenDrawingOrderEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getChildDrawingOrder(int childCount, int i) {
|
||||
if (mChildDrawingOrderDelegate == null) {
|
||||
return super.getChildDrawingOrder(childCount, i);
|
||||
} else {
|
||||
return mChildDrawingOrderDelegate.getChildDrawingOrder(this, i);
|
||||
}
|
||||
}
|
||||
|
||||
public void setChildDrawingOrderDelegate(@Nullable ChildDrawingOrderDelegate delegate) {
|
||||
setChildrenDrawingOrderEnabled(delegate != null);
|
||||
mChildDrawingOrderDelegate = delegate;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue