mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
Perf improvements to RecyclerViewBackedScrollView.
Differential Revision: D2641500 fb-gh-sync-id: 7ec6e2863bccebc98f75f586c0f17d509808d46b
This commit is contained in:
parent
14b36b472e
commit
5695ebdcf4
@ -461,10 +461,6 @@ var ScrollResponderMixin = {
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardWillHide', this.scrollResponderKeyboardWillHide);
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidShow', this.scrollResponderKeyboardDidShow);
|
||||
this.addListenerOn(RCTDeviceEventEmitter, 'keyboardDidHide', this.scrollResponderKeyboardDidHide);
|
||||
warning(this.getInnerViewNode, 'You need to implement getInnerViewNode in '
|
||||
+ this.constructor.displayName + ' to get full'
|
||||
+ 'functionality from ScrollResponder mixin. See example of ListView and'
|
||||
+ ' ScrollView.');
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -9,6 +9,8 @@ var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var React = require('React');
|
||||
var ScrollResponder = require('ScrollResponder');
|
||||
var ScrollView = require('ScrollView');
|
||||
var View = require('View');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
@ -65,10 +67,6 @@ var RecyclerViewBackedScrollView = React.createClass({
|
||||
return this;
|
||||
},
|
||||
|
||||
getInnerViewNode: function(): any {
|
||||
return React.findNodeHandle(this.refs[INNERVIEW]);
|
||||
},
|
||||
|
||||
setNativeProps: function(props: Object) {
|
||||
this.refs[INNERVIEW].setNativeProps(props);
|
||||
},
|
||||
@ -93,11 +91,35 @@ var RecyclerViewBackedScrollView = React.createClass({
|
||||
style: ([{flex: 1}, this.props.style]: ?Array<any>),
|
||||
ref: INNERVIEW,
|
||||
};
|
||||
|
||||
var wrappedChildren = React.Children.map(this.props.children, (child) => {
|
||||
if (!child) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<View
|
||||
collapsable={false}
|
||||
style={styles.absolute}>
|
||||
{child}
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<NativeAndroidRecyclerView {...props}/>
|
||||
<NativeAndroidRecyclerView {...props}>
|
||||
{wrappedChildren}
|
||||
</NativeAndroidRecyclerView>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
absolute: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
});
|
||||
|
||||
var NativeAndroidRecyclerView = requireNativeComponent('AndroidRecyclerViewBackedScrollView', null);
|
||||
|
@ -3,9 +3,7 @@
|
||||
package com.facebook.react.views.recyclerview;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
@ -84,10 +82,72 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JavaScript ListView implementation rely on getting correct scroll offset. This class helps
|
||||
* with calculating that "real" offset of items in recycler view as those are not provided by
|
||||
* android widget implementation ({@link #onScrollChanged} is called with offset 0). We can't use
|
||||
* onScrolled either as we need to take into account that if height of element that is not above
|
||||
* the visible window changes the real scroll offset will change too, but onScrolled will only
|
||||
* give us scroll deltas that comes from the user interaction.
|
||||
*
|
||||
* This class helps in calculating "real" offset of row at specified index. It's used from
|
||||
* {@link #onScrollChanged} to query for the first visible index. Since while scrolling the
|
||||
* queried index will usually increment or decrement by one it's optimize to return result in
|
||||
* that common case very quickly.
|
||||
*/
|
||||
private static class ScrollOffsetTracker {
|
||||
|
||||
private final ReactListAdapter mReactListAdapter;
|
||||
|
||||
private int mLastRequestedPosition;
|
||||
private int mOffsetForLastPosition;
|
||||
|
||||
private ScrollOffsetTracker(ReactListAdapter reactListAdapter) {
|
||||
mReactListAdapter = reactListAdapter;
|
||||
}
|
||||
|
||||
public void onHeightChange(int index, int oldHeight, int newHeight) {
|
||||
if (index < mLastRequestedPosition) {
|
||||
mOffsetForLastPosition = (mOffsetForLastPosition - oldHeight + newHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public int getTopOffsetForItem(int index) {
|
||||
if (mLastRequestedPosition != index) {
|
||||
int sum = 0;
|
||||
int startIndex = 0;
|
||||
if (mLastRequestedPosition < index) {
|
||||
if (mLastRequestedPosition != -1) {
|
||||
sum = mOffsetForLastPosition;
|
||||
startIndex = mLastRequestedPosition;
|
||||
}
|
||||
for (int i = startIndex; i < index; i++) {
|
||||
sum += mReactListAdapter.mViews.get(i).getMeasuredHeight();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (index < (mLastRequestedPosition - index)) {
|
||||
for (int i = 0; i < index; i++) {
|
||||
sum += mReactListAdapter.mViews.get(i).getMeasuredHeight();
|
||||
}
|
||||
} else {
|
||||
for (int i = mLastRequestedPosition - 1; i >= index; i--) {
|
||||
sum -= mReactListAdapter.mViews.get(i).getMeasuredHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
mLastRequestedPosition = index;
|
||||
mOffsetForLastPosition = sum;
|
||||
}
|
||||
return mOffsetForLastPosition;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*package*/ static class ReactListAdapter extends Adapter<ConcreteViewHolder> {
|
||||
|
||||
private final List<View> mViews = new ArrayList<>();
|
||||
private final Map<View, Integer> mTopOffsetsFromLayout = new HashMap<>();
|
||||
private final ScrollOffsetTracker mScrollOffsetTracker;
|
||||
private int mTotalChildrenHeight = 0;
|
||||
|
||||
// The following `OnLayoutChangeListsner` is attached to the views stored in the adapter
|
||||
@ -96,8 +156,6 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
private final View.OnLayoutChangeListener
|
||||
mChildLayoutChangeListener = new View.OnLayoutChangeListener() {
|
||||
|
||||
private boolean mReentrant = false;
|
||||
|
||||
@Override
|
||||
public void onLayoutChange(
|
||||
View v,
|
||||
@ -109,27 +167,14 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
int oldTop,
|
||||
int oldRight,
|
||||
int oldBottom) {
|
||||
// We need to get layout information from css-layout to set the size of the rows correctly
|
||||
// and we also use top position that is calculated there to provide correct offset for the
|
||||
// scroll events.
|
||||
// To achieve both we first store updated top position. Then we call layout again to
|
||||
// re-layout view at (0,0) position because each view cell needs a position in relative
|
||||
// coordinates. To prevent from this event being triggered when we call layout again, we
|
||||
// use `mReentrant` boolean as a guard.
|
||||
// We need to get layout information from css-layout to set the size of the rows correctly.
|
||||
|
||||
if (!mReentrant) {
|
||||
int oldHeight = (oldBottom - oldTop);
|
||||
int newHeight = (bottom - top);
|
||||
int width = right - left;
|
||||
int oldHeight = (oldBottom - oldTop);
|
||||
int newHeight = (bottom - top);
|
||||
|
||||
// Update top positions cache and total height
|
||||
mTopOffsetsFromLayout.put(v, top);
|
||||
if (oldHeight != newHeight) {
|
||||
mTotalChildrenHeight = mTotalChildrenHeight - oldHeight + newHeight;
|
||||
|
||||
// We need to re-layout view to place it in relative coordinates of cell wrapper -> (0,0)
|
||||
mReentrant = true;
|
||||
v.layout(0, 0, width, newHeight);
|
||||
mReentrant = false;
|
||||
mScrollOffsetTracker.onHeightChange(mViews.indexOf(v), oldHeight, newHeight);
|
||||
|
||||
// Since "wrapper" view position +dimensions are not managed by NativeViewHierarchyManager
|
||||
// we need to ensure that the wrapper view is properly layed out as it dimension should
|
||||
@ -142,7 +187,7 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
// update dimensions of them through overridden onMeasure method.
|
||||
// We don't care about calling this is the view is not currently attached as it would be
|
||||
// laid out once added to the recycler.
|
||||
if (newHeight != oldHeight && v.getParent() != null
|
||||
if (v.getParent() != null
|
||||
&& v.getParent().getParent() != null) {
|
||||
View wrapper = (View) v.getParent(); // native view that wraps view added to adapter
|
||||
wrapper.forceLayout();
|
||||
@ -156,6 +201,7 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
};
|
||||
|
||||
public ReactListAdapter() {
|
||||
mScrollOffsetTracker = new ScrollOffsetTracker(this);
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@ -163,21 +209,19 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
mViews.add(index, child);
|
||||
|
||||
mTotalChildrenHeight += child.getMeasuredHeight();
|
||||
mTopOffsetsFromLayout.put(child, child.getTop());
|
||||
child.addOnLayoutChangeListener(mChildLayoutChangeListener);
|
||||
|
||||
notifyDataSetChanged();
|
||||
notifyItemInserted(index);
|
||||
}
|
||||
|
||||
public void removeViewAt(int index) {
|
||||
View child = mViews.get(index);
|
||||
if (child != null) {
|
||||
mViews.remove(index);
|
||||
mTopOffsetsFromLayout.remove(child);
|
||||
child.removeOnLayoutChangeListener(mChildLayoutChangeListener);
|
||||
mTotalChildrenHeight -= child.getMeasuredHeight();
|
||||
|
||||
notifyDataSetChanged();
|
||||
notifyItemRemoved(index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,8 +264,7 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
}
|
||||
|
||||
public int getTopOffsetForItem(int index) {
|
||||
return Assertions.assertNotNull(
|
||||
mTopOffsetsFromLayout.get(Assertions.assertNotNull(mViews.get(index))));
|
||||
return mScrollOffsetTracker.getTopOffsetForItem(index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,7 +272,7 @@ public class RecyclerViewBackedScrollView extends RecyclerView {
|
||||
int offsetY = 0;
|
||||
if (getChildCount() > 0) {
|
||||
View recyclerViewChild = getChildAt(0);
|
||||
int childPosition = getChildAdapterPosition(recyclerViewChild);
|
||||
int childPosition = getChildViewHolder(recyclerViewChild).getLayoutPosition();
|
||||
offsetY = ((ReactListAdapter) getAdapter()).getTopOffsetForItem(childPosition) -
|
||||
recyclerViewChild.getTop();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user