Reverted commit D3827366

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

Pulled By: fred2028

fbshipit-source-id: d346068734c5b987518794ab23e13914ed13b5c4
This commit is contained in:
Janic Duplessis
2016-09-15 12:01:25 -07:00
committed by Facebook Github Bot 3
parent c1ed6ab4ed
commit d0d1712851
14 changed files with 24 additions and 266 deletions

View File

@@ -9,6 +9,10 @@
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;
@@ -20,21 +24,13 @@ 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.react.views.view.ReactViewGroup;
import com.facebook.react.views.view.ReactViewManager;
import java.lang.reflect.Field;
import javax.annotation.Nullable;
import com.facebook.infer.annotation.Assertions;
/**
* A simple subclass of ScrollView that doesn't dispatch measure and layout to its children and has
@@ -62,16 +58,6 @@ 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);
@@ -79,10 +65,6 @@ 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) {
@@ -157,7 +139,6 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou
super.onScrollChanged(x, y, oldX, oldY);
if (mOnScrollDispatchHelper.onScrollChanged(x, y)) {
dockClosestSectionHeader();
if (mRemoveClippedSubviews) {
updateClippingRect();
}
@@ -328,84 +309,6 @@ 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) {

View File

@@ -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,11 +104,6 @@ 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();

View File

@@ -13,9 +13,7 @@ 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;
@@ -36,17 +34,13 @@ import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
* initializes most of the storage needed for them.
*/
public class ReactViewGroup extends ViewGroup implements
ReactInterceptingViewGroup, ReactClippingViewGroup, ReactPointerEventsView, ReactHitSlopView, DrawingOrderViewGroup {
ReactInterceptingViewGroup, com.facebook.react.uimanager.ReactClippingViewGroup, ReactPointerEventsView, ReactHitSlopView {
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 RectF sHelperRect = new RectF();
public interface ChildDrawingOrderDelegate {
int getChildDrawingOrder(ReactViewGroup view, int i);
}
private static final Rect sHelperRect = new Rect();
/**
* This listener will be set for child views when removeClippedSubview property is enabled. When
@@ -99,7 +93,6 @@ 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);
@@ -297,15 +290,8 @@ 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());
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 intersects = clippingRect
.intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, 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
@@ -351,15 +337,8 @@ public class ReactViewGroup extends ViewGroup implements
// do fast check whether intersect state changed
sHelperRect.set(subview.getLeft(), subview.getTop(), subview.getRight(), subview.getBottom());
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));
boolean intersects = mClippingRect
.intersects(sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom);
// If it was intersecting before, should be attached to the parent
boolean oldIntersects = (subview.getParent() != null);
@@ -552,27 +531,4 @@ 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;
}
}