Permalink
Browse files

Further improvements in RecyclerViewBackedScrollView.

Summary: public
Changed ListView to use onLayout and onContentSizeChange (new) events instead of measure. Updated ScrollView implementation to support contentSizeChange event with an implementation based on onLayout attached to the content view. For RecyclerViewBackedScrollView we need to generate that event directly as it doesn't have a concept of content view.
This greatly improves performance of ListView that uses RecyclerViewBackedScrollView

Reviewed By: mkonicek

Differential Revision: D2679460

fb-gh-sync-id: ba26462d9d3b071965cbe46314f89f0dcfd9db9f
  • Loading branch information...
kmagiera authored and facebook-github-bot-6 committed Nov 20, 2015
1 parent 848a151 commit 1195f9c8e8e47c25d90993218f9ea63123d71cc3
@@ -71,6 +71,11 @@ var RecyclerViewBackedScrollView = React.createClass({
this.refs[INNERVIEW].setNativeProps(props);
},
_handleContentSizeChange: function(event) {
var {width, height} = event.nativeEvent;
this.props.onContentSizeChange(width, height);
},
render: function() {
var props = {
...this.props,
@@ -92,6 +97,10 @@ var RecyclerViewBackedScrollView = React.createClass({
ref: INNERVIEW,
};
if (this.props.onContentSizeChange) {
props.onContentSizeChange = this._handleContentSizeChange;
}
var wrappedChildren = React.Children.map(this.props.children, (child) => {
if (!child) {
return null;
@@ -193,6 +193,12 @@ var ScrollView = React.createClass({
* @platform ios
*/
onScrollAnimationEnd: PropTypes.func,
/**
* Called when scrollable content view of the ScrollView changes. It's
* implemented using onLayout handler attached to the content container
* which this ScrollView renders.
*/
onContentSizeChange: PropTypes.func,
/**
* When true, the scroll view stops on multiples of the scroll view's size
* when scrolling. This can be used for horizontal pagination. The default
@@ -360,6 +366,11 @@ var ScrollView = React.createClass({
this.scrollResponderHandleScroll(e);
},
_handleContentOnLayout: function(event) {
var {width, height} = event.nativeEvent.layout;
this.props.onContentSizeChange && this.props.onContentSizeChange(width, height);
},
render: function() {
var contentContainerStyle = [
this.props.horizontal && styles.contentContainerHorizontal,
@@ -376,8 +387,16 @@ var ScrollView = React.createClass({
);
}
var contentSizeChangeProps = {};
if (this.props.onContentSizeChange) {
contentSizeChangeProps = {
onLayout: this._handleContentOnLayout,
};
}
var contentContainer =
<View
{...contentSizeChangeProps}
ref={INNERVIEW}
style={contentContainerStyle}
removeClippedSubviews={this.props.removeClippedSubviews}
@@ -406,6 +406,8 @@ var ListView = React.createClass({
// component's original ref instead of clobbering it
return React.cloneElement(renderScrollComponent(props), {
ref: SCROLLVIEW_REF,
onContentSizeChange: this._onContentSizeChange,
onLayout: this._onLayout,
}, header, bodyComponents, footer);
},
@@ -418,17 +420,6 @@ var ListView = React.createClass({
if (!scrollComponent || !scrollComponent.getInnerViewNode) {
return;
}
RCTUIManager.measureLayout(
scrollComponent.getInnerViewNode(),
React.findNodeHandle(scrollComponent),
logError,
this._setScrollContentLength
);
RCTUIManager.measureLayoutRelativeToParent(
React.findNodeHandle(scrollComponent),
logError,
this._setScrollVisibleLength
);
// RCTScrollViewManager.calculateChildFrames is not available on
// every platform
@@ -439,9 +430,19 @@ var ListView = React.createClass({
);
},
_setScrollContentLength: function(left, top, width, height) {
_onContentSizeChange: function(width, height) {
this.scrollProperties.contentLength = !this.props.horizontal ?
height : width;
this._updateVisibleRows();
this._renderMoreRowsIfNeeded();
},
_onLayout: function(event) {
var {width, height} = event.nativeEvent.layout;
this.scrollProperties.visibleLength = !this.props.horizontal ?
height : width;
this._updateVisibleRows();
this._renderMoreRowsIfNeeded();
},
_setScrollVisibleLength: function(left, top, width, height) {
@@ -0,0 +1,40 @@
// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.views.recyclerview;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Event dispatched by {@link RecyclerViewBackedScrollView} when total height of it's children
* changes
*/
public class ContentSizeChangeEvent extends Event<ContentSizeChangeEvent> {
public static final String EVENT_NAME = "topContentSizeChange";
private final int mWidth;
private final int mHeight;
public ContentSizeChangeEvent(int viewTag, long timestampMs, int width, int height) {
super(viewTag, timestampMs);
mWidth = width;
mHeight = height;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
WritableMap data = Arguments.createMap();
data.putDouble("width", PixelUtil.toDIPFromPixel(mWidth));
data.putDouble("height", PixelUtil.toDIPFromPixel(mHeight));
rctEventEmitter.receiveEvent(getViewTag(), EVENT_NAME, data);
}
}
@@ -148,6 +148,7 @@ public int getTopOffsetForItem(int index) {
private final List<View> mViews = new ArrayList<>();
private final ScrollOffsetTracker mScrollOffsetTracker;
private final RecyclerViewBackedScrollView mScrollView;
private int mTotalChildrenHeight = 0;
// The following `OnLayoutChangeListsner` is attached to the views stored in the adapter
@@ -173,7 +174,7 @@ public void onLayoutChange(
int newHeight = (bottom - top);
if (oldHeight != newHeight) {
mTotalChildrenHeight = mTotalChildrenHeight - oldHeight + newHeight;
updateTotalChildrenHeight(newHeight - oldHeight);
mScrollOffsetTracker.onHeightChange(mViews.indexOf(v), oldHeight, newHeight);
// Since "wrapper" view position +dimensions are not managed by NativeViewHierarchyManager
@@ -200,15 +201,16 @@ public void onLayoutChange(
}
};
public ReactListAdapter() {
public ReactListAdapter(RecyclerViewBackedScrollView scrollView) {
mScrollView = scrollView;
mScrollOffsetTracker = new ScrollOffsetTracker(this);
setHasStableIds(true);
}
public void addView(View child, int index) {
mViews.add(index, child);
mTotalChildrenHeight += child.getMeasuredHeight();
updateTotalChildrenHeight(child.getMeasuredHeight());
child.addOnLayoutChangeListener(mChildLayoutChangeListener);
notifyItemInserted(index);
@@ -219,12 +221,19 @@ public void removeViewAt(int index) {
if (child != null) {
mViews.remove(index);
child.removeOnLayoutChangeListener(mChildLayoutChangeListener);
mTotalChildrenHeight -= child.getMeasuredHeight();
updateTotalChildrenHeight(-child.getMeasuredHeight());
notifyItemRemoved(index);
}
}
private void updateTotalChildrenHeight(int delta) {
if (delta != 0) {
mTotalChildrenHeight += delta;
mScrollView.onTotalChildrenHeightChange(mTotalChildrenHeight);
}
}
@Override
public ConcreteViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ConcreteViewHolder(new RecyclableWrapperViewGroup(parent.getContext()));
@@ -268,6 +277,12 @@ public int getTopOffsetForItem(int index) {
}
}
private boolean mSendContentSizeChangeEvents;
public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
mSendContentSizeChangeEvents = sendContentSizeChangeEvents;
}
private int calculateAbsoluteOffset() {
int offsetY = 0;
if (getChildCount() > 0) {
@@ -304,12 +319,23 @@ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
getHeight()));
}
private void onTotalChildrenHeightChange(int newTotalChildrenHeight) {
if (mSendContentSizeChangeEvents) {
((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher()
.dispatchEvent(new ContentSizeChangeEvent(
getId(),
SystemClock.uptimeMillis(),
getWidth(),
newTotalChildrenHeight));
}
}
public RecyclerViewBackedScrollView(Context context) {
super(context);
setHasFixedSize(true);
setItemAnimator(new NotAnimatedItemAnimator());
setLayoutManager(new LinearLayoutManager(context));
setAdapter(new ReactListAdapter());
setAdapter(new ReactListAdapter(this));
}
/*package*/ void addViewToAdapter(View child, int index) {
@@ -4,12 +4,17 @@
import javax.annotation.Nullable;
import java.util.Map;
import android.view.View;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.ReactProp;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.views.scroll.ReactScrollViewCommandHelper;
import com.facebook.react.views.scroll.ScrollEvent;
/**
* View manager for {@link RecyclerViewBackedScrollView}.
@@ -27,6 +32,11 @@ public String getName() {
// TODO(8624925): Implement removeClippedSubviews support for native ListView
@ReactProp(name = "onContentSizeChange")
public void setOnContentSizeChange(RecyclerViewBackedScrollView view, boolean value) {
view.setSendContentSizeChangeEvents(value);
}
@Override
protected RecyclerViewBackedScrollView createViewInstance(ThemedReactContext reactContext) {
return new RecyclerViewBackedScrollView(reactContext);
@@ -76,4 +86,15 @@ public void scrollWithoutAnimationTo(
ReactScrollViewCommandHelper.ScrollToCommandData data) {
view.scrollTo(data.mDestX, data.mDestY, false);
}
@Override
public @Nullable
Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.builder()
.put(ScrollEvent.EVENT_NAME, MapBuilder.of("registrationName", "onScroll"))
.put(
ContentSizeChangeEvent.EVENT_NAME,
MapBuilder.of("registrationName", "onContentSizeChange"))
.build();
}
}

3 comments on commit 1195f9c

@chirag04

This comment has been minimized.

Show comment
Hide comment
@chirag04
Collaborator

chirag04 replied Nov 20, 2015

@kmagiera

This comment has been minimized.

Show comment
Hide comment
@kmagiera

kmagiera Nov 21, 2015

Contributor

I apologise.
@mkonicek fixed it with 155a609 - thank you!

Contributor

kmagiera replied Nov 21, 2015

I apologise.
@mkonicek fixed it with 155a609 - thank you!

@chirag04

This comment has been minimized.

Show comment
Hide comment
@chirag04

chirag04 Nov 21, 2015

Collaborator

no worries. i saw the fix. Thanks :)

Collaborator

chirag04 replied Nov 21, 2015

no worries. i saw the fix. Thanks :)

Please sign in to comment.