From 6ec5654e7a26b856dbe26b2ba74c15ec258f065e Mon Sep 17 00:00:00 2001 From: Martin Konicek Date: Thu, 2 Feb 2017 03:30:52 -0800 Subject: [PATCH] BREAKING: Move RecyclerViewBackedScrollView out of open source Summary: `RecyclerViewBackedScrollView` was added a long time ago to work around the scroll-back-when-data-is-added bug, but that has now been fixed directly in the `ScrollView` (`ReactScrollView.java`) in open source and internally. Reviewed By: astreet Differential Revision: D4482105 fbshipit-source-id: 208f21f00045d5c5a83b74ad69b3db6fa63391d7 --- .../RecyclerViewBackedScrollView.android.js | 189 --------- .../RecyclerViewBackedScrollView.ios.js | 8 - Libraries/react-native/react-native.js | 1 - .../java/com/facebook/react/tests/BUCK | 2 +- .../main/java/com/facebook/react/shell/BUCK | 2 +- .../react/shell/MainReactPackage.java | 2 - .../facebook/react/views/recyclerview/BUCK | 23 - .../recyclerview/NotAnimatedItemAnimator.java | 71 ---- .../RecyclerViewBackedScrollView.java | 397 ------------------ .../RecyclerViewBackedScrollViewManager.java | 99 ----- 10 files changed, 2 insertions(+), 792 deletions(-) delete mode 100644 Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js delete mode 100644 Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/NotAnimatedItemAnimator.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java diff --git a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js deleted file mode 100644 index b86a82f90..000000000 --- a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.android.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule RecyclerViewBackedScrollView - */ -'use strict'; - -var React = require('React'); -var ScrollResponder = require('ScrollResponder'); -var ScrollView = require('ScrollView'); -var View = require('View'); -var StyleSheet = require('StyleSheet'); - -var requireNativeComponent = require('requireNativeComponent'); - -var INNERVIEW = 'InnerView'; - -/** - * RecyclerViewBackedScrollView is DEPRECATED and will be removed from - * React Native. - * Please use a `ListView` which has `removeClippedSubviews` enabled by - * default so that rows that are out of sight are automatically - * detached from the view hierarchy. - * - * Wrapper around Android native recycler view. - * - * It simply renders rows passed as children in a separate recycler view cells - * similarly to how `ScrollView` is doing it. Thanks to the fact that it uses - * native `RecyclerView` though, rows that are out of sight are going to be - * automatically detached (similarly on how this would work with - * `removeClippedSubviews = true` on a `ScrollView.js`). - * - * CAUTION: This is an experimental component and should only be used together - * with javascript implementation of list view (see ListView.js). In order to - * use it pass this component as `renderScrollComponent` to the list view. For - * now only horizontal scrolling is supported. - * - * Example: - * - * ``` - * getInitialState: function() { - * var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); - * return { - * dataSource: ds.cloneWithRows(['row 1', 'row 2']), - * }; - * }, - * - * render: function() { - * return ( - * {rowData}} - * renderScrollComponent={props => } - * /> - * ); - * }, - * ``` - */ -var RecyclerViewBackedScrollView = React.createClass({ - - propTypes: { - ...ScrollView.propTypes, - }, - - mixins: [ScrollResponder.Mixin], - - componentWillMount: function() { - console.warn( - 'RecyclerViewBackedScrollView is DEPRECATED and will be removed from React Native. ' + - 'Please use a ListView which has removeClippedSubviews enabled by default so that ' + - 'rows that are out of sight are automatically detached from the view hierarchy.') - }, - - getInitialState: function() { - return this.scrollResponderMixinGetInitialState(); - }, - - getScrollResponder: function() { - return this; - }, - - setNativeProps: function(props: Object) { - this.refs[INNERVIEW].setNativeProps(props); - }, - - _handleContentSizeChange: function(event) { - var {width, height} = event.nativeEvent; - this.props.onContentSizeChange(width, height); - }, - - /** - * A helper function to scroll to a specific point in the scrollview. - * This is currently used to help focus on child textviews, but can also - * be used to quickly scroll to any element we want to focus. Syntax: - * - * scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true}) - * - * Note: The weird argument signature is due to the fact that, for historical reasons, - * the function also accepts separate arguments as as alternative to the options object. - * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. - */ - scrollTo: function( - y?: number | { x?: number, y?: number, animated?: boolean }, - x?: number, - animated?: boolean - ) { - if (typeof y === 'number') { - console.warn('`scrollTo(y, x, animated)` is deprecated. Use `scrollTo({x: 5, y: 5, animated: true})` instead.'); - } else { - ({x, y, animated} = y || {}); - } - this.getScrollResponder().scrollResponderScrollTo({x: x || 0, y: y || 0, animated: animated !== false}); - }, - - render: function() { - var recyclerProps = { - ...this.props, - onTouchStart: this.scrollResponderHandleTouchStart, - onTouchMove: this.scrollResponderHandleTouchMove, - onTouchEnd: this.scrollResponderHandleTouchEnd, - onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag, - onScrollEndDrag: this.scrollResponderHandleScrollEndDrag, - onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin, - onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd, - onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, - onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, - onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, - onResponderGrant: this.scrollResponderHandleResponderGrant, - onResponderRelease: this.scrollResponderHandleResponderRelease, - onResponderReject: this.scrollResponderHandleResponderReject, - onScroll: this.scrollResponderHandleScroll, - ref: INNERVIEW, - }; - - if (this.props.onContentSizeChange) { - recyclerProps.onContentSizeChange = this._handleContentSizeChange; - } - - var wrappedChildren = React.Children.map(this.props.children, (child) => { - if (!child) { - return null; - } - return ( - - {child} - - ); - }); - - const refreshControl = this.props.refreshControl; - if (refreshControl) { - // Wrap the NativeAndroidRecyclerView with a AndroidSwipeRefreshLayout. - return React.cloneElement( - refreshControl, - {style: [styles.base, this.props.style]}, - - {wrappedChildren} - - ); - } - - return ( - - {wrappedChildren} - - ); - }, -}); - -var styles = StyleSheet.create({ - absolute: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - }, - base: { - flex: 1, - }, -}); - -var NativeAndroidRecyclerView = requireNativeComponent( - 'AndroidRecyclerViewBackedScrollView', - RecyclerViewBackedScrollView -); - -module.exports = RecyclerViewBackedScrollView; diff --git a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js b/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js deleted file mode 100644 index a84377e54..000000000 --- a/Libraries/Components/ScrollView/RecyclerViewBackedScrollView.ios.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright 2004-present Facebook. All Rights Reserved. - * - * @providesModule RecyclerViewBackedScrollView - */ -'use strict'; - -module.exports = require('ScrollView'); diff --git a/Libraries/react-native/react-native.js b/Libraries/react-native/react-native.js index c62dbda68..88c418776 100644 --- a/Libraries/react-native/react-native.js +++ b/Libraries/react-native/react-native.js @@ -50,7 +50,6 @@ const ReactNative = { get Slider() { return require('Slider'); }, get SnapshotViewIOS() { return require('SnapshotViewIOS'); }, get Switch() { return require('Switch'); }, - get RecyclerViewBackedScrollView() { return require('RecyclerViewBackedScrollView'); }, get RefreshControl() { return require('RefreshControl'); }, get StatusBar() { return require('StatusBar'); }, get SwipeableListView() { return require('SwipeableListView'); }, diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK index 528021311..99e28e028 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK @@ -1,6 +1,7 @@ include_defs('//ReactAndroid/DEFS') deps = [ + '//java/com/facebook/fbreact/views/recyclerview:recyclerview', react_native_dep('third-party/android/support/v4:lib-support-v4'), react_native_dep('third-party/java/jsr-305:jsr-305'), react_native_dep('third-party/java/junit:junit'), @@ -19,7 +20,6 @@ deps = [ react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), react_native_target('java/com/facebook/react/views/picker:picker'), react_native_target('java/com/facebook/react/views/progressbar:progressbar'), - react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'), react_native_target('java/com/facebook/react/views/scroll:scroll'), react_native_target('java/com/facebook/react/views/slider:slider'), react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK index 577a832de..8f9c023a5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/BUCK @@ -4,6 +4,7 @@ android_library( name = 'shell', srcs = glob(['**/*.java']), deps = [ + '//java/com/facebook/fbreact/views/recyclerview:recyclerview', react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'), react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), react_native_dep('third-party/android/support/v4:lib-support-v4'), @@ -45,7 +46,6 @@ android_library( react_native_target('java/com/facebook/react/views/modal:modal'), react_native_target('java/com/facebook/react/views/picker:picker'), react_native_target('java/com/facebook/react/views/progressbar:progressbar'), - react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'), react_native_target('java/com/facebook/react/views/scroll:scroll'), react_native_target('java/com/facebook/react/views/slider:slider'), react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'), diff --git a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java index 94b535a39..db3473a18 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java @@ -67,7 +67,6 @@ import com.facebook.react.views.modal.ReactModalHostManager; import com.facebook.react.views.picker.ReactDialogPickerManager; import com.facebook.react.views.picker.ReactDropdownPickerManager; import com.facebook.react.views.progressbar.ReactProgressBarViewManager; -import com.facebook.react.views.recyclerview.RecyclerViewBackedScrollViewManager; import com.facebook.react.views.scroll.ReactHorizontalScrollViewManager; import com.facebook.react.views.scroll.ReactScrollViewManager; import com.facebook.react.views.slider.ReactSliderManager; @@ -275,7 +274,6 @@ public class MainReactPackage extends LazyReactPackage { viewManagers.add(new ReactViewPagerManager()); viewManagers.add(new ReactVirtualTextViewManager()); viewManagers.add(new ReactWebViewManager()); - viewManagers.add(new RecyclerViewBackedScrollViewManager()); viewManagers.add(new SwipeRefreshLayoutManager()); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(reactContext); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK deleted file mode 100644 index cb97effd8..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/BUCK +++ /dev/null @@ -1,23 +0,0 @@ -include_defs('//ReactAndroid/DEFS') - -android_library( - name = 'recyclerview', - srcs = glob(['**/*.java']), - deps = [ - react_native_dep('third-party/android/support/v4:lib-support-v4'), - react_native_dep('third-party/android/support/v7/recyclerview:recyclerview'), - react_native_dep('third-party/java/infer-annotations:infer-annotations'), - react_native_dep('third-party/java/jsr-305:jsr-305'), - react_native_target('java/com/facebook/react/bridge:bridge'), - react_native_target('java/com/facebook/react/common:common'), - react_native_target('java/com/facebook/react/touch:touch'), - react_native_target('java/com/facebook/react/uimanager:uimanager'), - react_native_target('java/com/facebook/react/uimanager/annotations:annotations'), - react_native_target('java/com/facebook/react/views/scroll:scroll'), - react_native_target('java/com/facebook/react/views/view:view'), - ], - visibility = [ - 'PUBLIC', - ], -) - diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/NotAnimatedItemAnimator.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/NotAnimatedItemAnimator.java deleted file mode 100644 index cc12b8c82..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/NotAnimatedItemAnimator.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.views.recyclerview; - -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.SimpleItemAnimator; - -/** - * Implementation of {@link RecyclerView.ItemAnimator} that disables all default animations. - */ -/*package*/ class NotAnimatedItemAnimator extends SimpleItemAnimator { - - @Override - public void runPendingAnimations() { - // nothing - } - - @Override - public boolean animateRemove(RecyclerView.ViewHolder holder) { - dispatchRemoveStarting(holder); - dispatchRemoveFinished(holder); - return true; - } - - @Override - public boolean animateAdd(RecyclerView.ViewHolder holder) { - dispatchAddStarting(holder); - dispatchAddFinished(holder); - return true; - } - - @Override - public boolean animateMove( - RecyclerView.ViewHolder holder, - int fromX, - int fromY, - int toX, - int toY) { - dispatchMoveStarting(holder); - dispatchMoveFinished(holder); - return true; - } - - @Override - public boolean animateChange( - RecyclerView.ViewHolder oldHolder, - RecyclerView.ViewHolder newHolder, - int fromLeft, - int fromTop, - int toLeft, - int toTop) { - dispatchChangeStarting(oldHolder, true); - dispatchChangeFinished(oldHolder, true); - dispatchChangeStarting(newHolder, false); - dispatchChangeFinished(newHolder, false); - return true; - } - - @Override - public void endAnimation(RecyclerView.ViewHolder item) { - } - - @Override - public void endAnimations() { - } - - @Override - public boolean isRunning() { - return false; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java deleted file mode 100644 index d823ac88b..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollView.java +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -package com.facebook.react.views.recyclerview; - -import java.util.ArrayList; -import java.util.List; - -import android.content.Context; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; - -import com.facebook.infer.annotation.Assertions; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.common.annotations.VisibleForTesting; -import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.events.ContentSizeChangeEvent; -import com.facebook.react.uimanager.events.NativeGestureUtil; -import com.facebook.react.views.scroll.ScrollEvent; -import com.facebook.react.views.scroll.ScrollEventType; - -/** - * Wraps {@link RecyclerView} providing interface similar to `ScrollView.js` where each children - * will be rendered as a separate {@link RecyclerView} row. - * - * Currently supports only vertically positioned item. Views will not be automatically recycled but - * they will be detached from native view hierarchy when scrolled offscreen. - * - * It works by storing all child views in an array within adapter and binding appropriate views to - * rows when requested. - */ -@VisibleForTesting -public class RecyclerViewBackedScrollView extends RecyclerView { - - /** - * Simple implementation of {@link ViewHolder} as it's an abstract class. The only thing we need - * to hold in this implementation is the reference to {@link RecyclableWrapperViewGroup} that - * is already stored by default. - */ - private static class ConcreteViewHolder extends ViewHolder { - public ConcreteViewHolder(View itemView) { - super(itemView); - } - } - - /** - * View that is going to be used as a cell in {@link RecyclerView}. It's going to be reusable and - * we will remove/attach views for a certain positions based on the {@code mViews} array stored - * in the adapter class. - * - * This method overrides {@link #onMeasure} and delegates measurements to the child view that has - * been attached to. This is because instances of {@link RecyclableWrapperViewGroup} are created - * outside of {@link NativeViewHierarchyManager} and their layout is not managed by that manager - * as opposed to all the other react-native views. Instead we use dimensions of the child view - * (dimensions has been set in layouting process) so that size of this view match the size of - * the view it wraps. - */ - private static class RecyclableWrapperViewGroup extends ViewGroup { - - public RecyclableWrapperViewGroup(Context context) { - super(context); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - // This view will only have one child that is managed by the `NativeViewHierarchyManager` and - // its position and dimensions are set separately. We don't need to handle its layouting here - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (getChildCount() > 0) { - // We override measure spec and use dimensions of the children. Children is a view added - // from the adapter and always have a correct dimensions specified as they are calculated - // and set with NativeViewHierarchyManager - View child = getChildAt(0); - setMeasuredDimension(child.getMeasuredWidth(), child.getMeasuredHeight()); - } else { - Assertions.assertUnreachable("RecyclableWrapperView measured but no view attached"); - } - } - } - - /** - * 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) { - // This method is frequently called from the "onScroll" handler of the "RecyclerView" with an - // index of first visible item of the view. Implementation of this method takes advantage of - // that fact by caching the value for the last index that this method has been called with. - // - // There are a 2 cases that we optimize for: - // 1) The visible item doesn't change between subsequent "onScroll" calls, in that case we - // don't need to calculate anything, just return the cached value - // 2) The next visible item will be the one that is adjacent to the item that we store the - // cached value for: index + 1 when scrolling down or index - 1 when scrolling up. Then it - // is sufficient to add/subtract height of item at the "last index" - // - // The implementation accounts for the cases when next index is not necessarily a subsequent - // number of the cached one. In which case we try to minimize the number of rows we will loop - // through. - if (mLastRequestedPosition != index) { - int sum; - - if (mLastRequestedPosition < index) { - // This can either happen when we're scrolling down or if the cached value has never been - // calculated - int startIndex; - - if (mLastRequestedPosition != -1) { - // We already have the value cached, let's use it and only add heights of the items - // starting at the index we have the cached value for - sum = mOffsetForLastPosition; - startIndex = mLastRequestedPosition; - } else { - sum = 0; - startIndex = 0; - } - - for (int i = startIndex; i < index; i++) { - sum += mReactListAdapter.mViews.get(i).getMeasuredHeight(); - } - } else { - // We are scrolling up, we can either use cached value and subtract heights of rows - // between mLastRequestPosition and index, or we can calculate the height starting from 0 - // (this can be quite a frequent case as well, when the list implements "jump to the top" - // action). We just go for the option that require less calculations - if (index < (mLastRequestedPosition - index)) { - // index is relatively small, it's faster to calculate the sum starting from 0 - sum = 0; - for (int i = 0; i < index; i++) { - sum += mReactListAdapter.mViews.get(i).getMeasuredHeight(); - } - } else { - // index is "closer" to the last cached index than it is to 0. We can reuse cached sum - // and calculate the new sum by subtracting heights of the elements between - // "mLastRequestPosition" and "index" - sum = mOffsetForLastPosition; - 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 { - - private final List 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 - // `mViews` array. It's used to get layout information passed to that view from css-layout - // and to update its layout to be enclosed in the wrapper view group. - private final View.OnLayoutChangeListener - mChildLayoutChangeListener = new View.OnLayoutChangeListener() { - - @Override - public void onLayoutChange( - View v, - int left, - int top, - int right, - int bottom, - int oldLeft, - int oldTop, - int oldRight, - int oldBottom) { - // We need to get layout information from css-layout to set the size of the rows correctly. - - int oldHeight = (oldBottom - oldTop); - int newHeight = (bottom - top); - - if (oldHeight != newHeight) { - updateTotalChildrenHeight(newHeight - oldHeight); - 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 - // be updated if the wrapped view dimensions are changed. - // To achieve that we call `forceLayout()` on the view modified and on `RecyclerView` - // instance (which is accessible with `v.getParent().getParent()` if the view is - // attached). We rely on NativeViewHierarchyManager to call `layout` on `RecyclerView` - // then, which will happen once all the children of `RecyclerView` have their layout - // updated. This will trigger `layout` call on attached wrapper nodes and will let us - // 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 (v.getParent() != null - && v.getParent().getParent() != null) { - View wrapper = (View) v.getParent(); // native view that wraps view added to adapter - wrapper.forceLayout(); - // wrapper.getParent() points to the recycler if the view is currently attached (it - // could be in "scrape" state when it is attached to recyclable wrapper but not to - // the recycler) - ((View) wrapper.getParent()).forceLayout(); - } - } - } - }; - - public ReactListAdapter(RecyclerViewBackedScrollView scrollView) { - mScrollView = scrollView; - mScrollOffsetTracker = new ScrollOffsetTracker(this); - setHasStableIds(true); - } - - public void addView(View child, int index) { - mViews.add(index, child); - - updateTotalChildrenHeight(child.getMeasuredHeight()); - child.addOnLayoutChangeListener(mChildLayoutChangeListener); - - notifyItemInserted(index); - } - - public void removeViewAt(int index) { - View child = mViews.get(index); - if (child != null) { - mViews.remove(index); - child.removeOnLayoutChangeListener(mChildLayoutChangeListener); - 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())); - } - - @Override - public void onBindViewHolder(ConcreteViewHolder holder, int position) { - RecyclableWrapperViewGroup vg = (RecyclableWrapperViewGroup) holder.itemView; - View row = mViews.get(position); - if (row.getParent() != vg) { - vg.addView(row, 0); - } - } - - @Override - public void onViewRecycled(ConcreteViewHolder holder) { - super.onViewRecycled(holder); - ((RecyclableWrapperViewGroup) holder.itemView).removeAllViews(); - } - - @Override - public int getItemCount() { - return mViews.size(); - } - - @Override - public long getItemId(int position) { - return mViews.get(position).getId(); - } - - public View getView(int index) { - return mViews.get(index); - } - - public int getTotalChildrenHeight() { - return mTotalChildrenHeight; - } - - public int getTopOffsetForItem(int index) { - return mScrollOffsetTracker.getTopOffsetForItem(index); - } - } - - private boolean mSendContentSizeChangeEvents; - - public void setSendContentSizeChangeEvents(boolean sendContentSizeChangeEvents) { - mSendContentSizeChangeEvents = sendContentSizeChangeEvents; - } - - private int calculateAbsoluteOffset() { - int offsetY = 0; - if (getChildCount() > 0) { - View recyclerViewChild = getChildAt(0); - int childPosition = getChildViewHolder(recyclerViewChild).getLayoutPosition(); - offsetY = ((ReactListAdapter) getAdapter()).getTopOffsetForItem(childPosition) - - recyclerViewChild.getTop(); - } - return offsetY; - } - - /*package*/ void scrollTo(int scrollX, int scrollY, boolean animated) { - int deltaY = scrollY - calculateAbsoluteOffset(); - if (animated) { - smoothScrollBy(0, deltaY); - } else { - scrollBy(0, deltaY); - } - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - - ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher() - .dispatchEvent(ScrollEvent.obtain( - getId(), - ScrollEventType.SCROLL, - 0, /* offsetX = 0, horizontal scrolling only */ - calculateAbsoluteOffset(), - getWidth(), - ((ReactListAdapter) getAdapter()).getTotalChildrenHeight(), - getWidth(), - getHeight())); - } - - private void onTotalChildrenHeightChange(int newTotalChildrenHeight) { - if (mSendContentSizeChangeEvents) { - ((ReactContext) getContext()).getNativeModule(UIManagerModule.class).getEventDispatcher() - .dispatchEvent(new ContentSizeChangeEvent( - getId(), - getWidth(), - newTotalChildrenHeight)); - } - } - - public RecyclerViewBackedScrollView(Context context) { - super(context); - setHasFixedSize(true); - setItemAnimator(new NotAnimatedItemAnimator()); - setLayoutManager(new LinearLayoutManager(context)); - setAdapter(new ReactListAdapter(this)); - } - - /*package*/ void addViewToAdapter(View child, int index) { - ((ReactListAdapter) getAdapter()).addView(child, index); - } - - /*package*/ void removeViewFromAdapter(int index) { - ((ReactListAdapter) getAdapter()).removeViewAt(index); - } - - /*package*/ View getChildAtFromAdapter(int index) { - return ((ReactListAdapter) getAdapter()).getView(index); - } - - /*package*/ int getChildCountFromAdapter() { - return getAdapter().getItemCount(); - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (super.onInterceptTouchEvent(ev)) { - NativeGestureUtil.notifyNativeGestureStarted(this, ev); - return true; - } - return false; - } -} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java deleted file mode 100644 index ca4586d76..000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/views/recyclerview/RecyclerViewBackedScrollViewManager.java +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2004-present Facebook. All Rights Reserved. - -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.annotations.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.ScrollEventType; - -/** - * View manager for {@link RecyclerViewBackedScrollView}. - */ -public class RecyclerViewBackedScrollViewManager extends - ViewGroupManager - implements ReactScrollViewCommandHelper.ScrollCommandHandler { - - private static final String REACT_CLASS = "AndroidRecyclerViewBackedScrollView"; - - @Override - public String getName() { - return REACT_CLASS; - } - - // 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); - } - - @Override - public void addView(RecyclerViewBackedScrollView parent, View child, int index) { - parent.addViewToAdapter(child, index); - } - - @Override - public int getChildCount(RecyclerViewBackedScrollView parent) { - return parent.getChildCountFromAdapter(); - } - - @Override - public View getChildAt(RecyclerViewBackedScrollView parent, int index) { - return parent.getChildAtFromAdapter(index); - } - - @Override - public void removeViewAt(RecyclerViewBackedScrollView parent, int index) { - parent.removeViewFromAdapter(index); - } - - /** - * Provides implementation of commands supported by {@link ReactScrollViewManager} - */ - @Override - public void receiveCommand( - RecyclerViewBackedScrollView view, - int commandId, - @Nullable ReadableArray args) { - ReactScrollViewCommandHelper.receiveCommand(this, view, commandId, args); - } - - @Override - public void scrollTo( - RecyclerViewBackedScrollView scrollView, - ReactScrollViewCommandHelper.ScrollToCommandData data) { - scrollView.scrollTo(data.mDestX, data.mDestY, data.mAnimated); - } - - @Override - public void scrollToEnd( - RecyclerViewBackedScrollView scrollView, - ReactScrollViewCommandHelper.ScrollToEndCommandData data) { - // Not implemented. - // RecyclerViewBackedScrollView is deprecated and will be removed. - // People should use a standard ScrollView or ListView instead. - } - - @Override - public @Nullable - Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.builder() - .put(ScrollEventType.SCROLL.getJSEventName(), MapBuilder.of("registrationName", "onScroll")) - .build(); - } -}