mirror of
https://github.com/status-im/react-native.git
synced 2025-01-28 02:04:55 +00:00
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
This commit is contained in:
parent
848a151ff8
commit
1195f9c8e8
@ -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 class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
|
||||
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 class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
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,7 +201,8 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
}
|
||||
};
|
||||
|
||||
public ReactListAdapter() {
|
||||
public ReactListAdapter(RecyclerViewBackedScrollView scrollView) {
|
||||
mScrollView = scrollView;
|
||||
mScrollOffsetTracker = new ScrollOffsetTracker(this);
|
||||
setHasStableIds(true);
|
||||
}
|
||||
@ -208,7 +210,7 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
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 class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
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 class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mSendContentSizeChangeEvents;
|
||||
|
||||
public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) {
|
||||
mSendContentSizeChangeEvents = sendContentSizeChangeEvents;
|
||||
}
|
||||
|
||||
private int calculateAbsoluteOffset() {
|
||||
int offsetY = 0;
|
||||
if (getChildCount() > 0) {
|
||||
@ -304,12 +319,23 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
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 @@ package com.facebook.react.views.recyclerview;
|
||||
|
||||
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 class RecyclerViewBackedScrollViewManager extends
|
||||
|
||||
// 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 class RecyclerViewBackedScrollViewManager extends
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user