Skip to content

Commit

Permalink
Sticky (#261)
Browse files Browse the repository at this point in the history
* Sticky Container init.

* Sticking view on scroll.

* Using view from row renderer.

* topStickyIndices done/

* Top and bottom stickies separated.

* Sticky header cascading and recycler react clone element

* Header cascading.

* Using StickyObject.

* Using Sticky Header.

* Sticky Footer used without multiplier.

* Sticky type context.

* Footer cascading working. Header and footer logic to be merged.

* Using Scroll component height.

* Previous sticky indice logic combined for header and footer.

* Next sticky index logic combined for header and footer.

* Sticky multiplier abstracted.

* Initial visibility abstracted and naming conventions.

* Container position.

* Refactoring.

* Getting rid of tentative sliding.

* Cleaning StickyContainer.

* Removed rerender.

* Computing layouts on indices change.

* Layout specifics abstracted.

* Init params and redundant methods removed.

* Using hashmap for visible indices.

* onVisibleIndexesChanged refactored.

* Calling super visible indices changed.

* Commented on receive props lines, modified todos.

* Todos.

* Recycler ref passed in visible indices changed and footer visibility dynamic.

* Deprecated visible indexes changed.

* Initial visibility bug fix.

* Resolved recylerRef any.

* Sending data in rowRenderer.

* Refactoring private vars.

* Fixes and refactoring.

* Resolved ref proxy.

* Scroll to handled for headers.

* Sticky Container prop types.

* Header scroll to todo removed and footer initial visibility bug fix.

* Indice to index...

* Sticky footer on load.

* Reverting type ScrollComponent.

* Refactoring.

* Custom recycler react element type.

* Correct child type asserted.

* Moved compute layouts to on scroll.

* Bug fixes.

* Compute layout bug fix.

* Resetting sticky view offset value bug fix.

* Bumped version.

* Made props optional.

* Added overrideRowRenderer and passing sticky layout type.

* Added overrideRowRenderer and passing sticky layout type.

* Override row renderer functional.

* rowRenderer type fix.

* Ts fix.

* Bumped sticky version.

* Sticky indices made function.

* Added compute layouts in visible indices changed.

* Bumped version.

* Reverting to array sticky params.

* Visible indices logic changed.

* Default offset 0.

* Removed maintaining visible indices.

* Renamed method.

* Renamed initial visibility.

* Made compute layouts public.

* Compute layouts again made private.

* Setting state after refs are obtained.

* Keeping init indices and calling after ref init.

* Bumping version.

* Removing unnecessary semicolons.

* Passing extended state in rowRenderer.

* Refactor.

* CR changes.

* CR changes.

* CR changes.

* Removing compute layouts from on scroll.

* Using all as sorted array.

* RLV dims and ficker bug fixes.

* Pagination  sticky removal bug fix.

* Bumped sticky version.

* Bumping version.

* Refactor.

* Build fixes and version bump.

* Adding style prop to container.

* Sorting indices.

* Not passing recyclerRef to Sticky object.

* Bumping version.

* Removed refs and undefined params.

* Bumped version.

* Edited doc.

* Binary search to find smallest index.

* Footer binary search added, refactoring, error handling.

* Bug fix.

* Removed unused method.

* Readme init.

* Doc, defs and sort removal.

* Sticky docs.

* Handling onBoundaryReached cases.

* Bumped version.

* Solved for sticky header initial.

* Boundary processing abstracted and footer solved.

* Bumped version.

* OnEndReached bug fix.

* Added onVisibleEndReached and using it instead of onEndReached.

* Flooring windowBound - offset. (#297)

* Boundary Processing (#298)

* Flooring windowBound - offset.

* Header boundary solved using scroll offset.

* Footer solved.

* Bug fix and doc updated.

* Version bump.

* Bounce scrolling recalculated on visible indices changed.

* Boundary reached method moved to object.

* Distance from window taken into consideration.

* Adding distance from window to window bound.

* Fix.

* Reverting process end reached code.

* Versions.

* Styling.

* Final Sticky Changes (#304)

* import was not from web (#299)

* CR changes.

* Throwing exceptions for visibleIndexes and array sort.

* Handling index 0 cases.

* Updated doc with errors.
  • Loading branch information
ananyachandra14 authored and naqvitalha committed Feb 11, 2019
1 parent d9ef3d4 commit 2965168
Show file tree
Hide file tree
Showing 13 changed files with 851 additions and 21 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Apart from all performance benefits RecyclerListView comes with great features o
- Non deterministic rendering mode on demand (height cannot be determined before rendering)
- (New) ItemAnimator interface added, customize to your will how RLV handles layout changes. Allows you to modify animations that move cells. You can do things like smoothly move an item to a new position when height of one of the cells has changed.
- (New) Stable Id support, ability to associate a stable id with an item. Will enable beautiful add/remove animations and optimize re-renders when DataProvider is updated.
- (New) Sticky recycler items that stick to either the top or bottom.

## Why?

Expand Down Expand Up @@ -79,6 +80,7 @@ For full feature set have a look at prop definitions of [RecyclerListView](https
## Guides
* **[Sample Code](https://github.com/Flipkart/recyclerlistview/tree/master/docs/guides/samplecode)**
* **[Performance](https://github.com/Flipkart/recyclerlistview/tree/master/docs/guides/performance)**
* **[Sticky Guide](https://github.com/Flipkart/recyclerlistview/tree/master/docs/guides/sticky)**
* **Web Support:** Works with React Native Web out of the box. For use with ReactJS start importing from `recyclerlistview/web` e.g., `import { RecyclerListView } from "recyclerlistview/web"`. Use aliases if you want to preserve import path. Only platform specific code is part of the build so, no unnecessary code will ship with your app.
* **Polyfills Needed:** `requestAnimationFrame`

Expand Down
23 changes: 23 additions & 0 deletions docs/guides/sticky/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Sticky Recycler Items Guide
* **[Sample Code](https://github.com/Flipkart/recyclerlistview/tree/master/docs/guides/sticky/sample)**

All you need to do to get started is wrap your `RecyclerListView` component with the `StickyContainer` component and pass either or both `stickyHeaderIndices` and `stickyFooterIndices`.

### 1) Important points to note
* `stickyHeaderIndices` and `stickyFooterIndices` should be sorted arrays, otherwise error will be thrown.
* `StickyContainer` should have a single child component of type `RecyclerListView` or any that extends it, otherwise error will be thrown.
* In your `RecyclerListView` component, pass ref as a function and not as a string, otherwise error will be thrown.
```js
<RecyclerListView ref={this._setRef}/>

_setRef(recycler) {
this._recyclerRef = recycler;
}
```
* If using `overrideRowRenderer`, keep in mind that upon scrolling to the very top or bottom of the content, stickies will be hidden. eg. If the first item in the list is given as sticky, scrolling to the top will display the original view and not the overridden view.

### 2) Props
* `stickyHeaderIndices` - An array of indices whose corresponding items need to be stuck to the top of the RecyclerListView once the items scroll off the top. Every subsequent sticky index view will push the previous sticky view off the top to take its place. Needs to be sorted ascending.
* `stickyFooterIndices` - Works same as sticky headers, but for views to be stuck at the bottom of the recyclerView. Needs to be sorted ascending.
* `overrideRowRenderer` - Optional. Will be called instead of rowRenderer for all sticky items. Any changes to the item for when they are stuck can be done here. Refer to sample code for usage.
* `style` - Optional. Pass the same style that is applied to the RecyclerListView component here.
94 changes: 94 additions & 0 deletions docs/guides/sticky/sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
```js
import React from 'react';
import {View} from 'react-native';
import {RecyclerListView, DataProvider, LayoutProvider} from 'recyclerlistview';
import StickyContainer from 'recyclerlistview/dist/reactnative/core/StickyContainer';
export default class StickySample extends React.Component {

constructor(props) {
super(props);
this._setRef = this._setRef.bind(this);

this._recyclerRef = null;
this.data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 6, 7, 8, 9, 10, 11];
this.dataProvider = new DataProvider((r1, r2) => {
return r1 !== r2;
});
this.dataProvider = this.dataProvider.cloneWithRows(this.data);
this.layoutProvider = new LayoutProvider(
index => {
return index;
},
(type, dimension) => {
dimension.height = 100;
dimension.width = 360;
}
);
}

_rowRenderer = (type, data, index) => {
let color = 'grey';
switch(index%6) {
case 0:
color = "purple";
break;
case 1:
color = "green";
break;
case 2:
color = "blue";
break;
case 3:
color = "red";
break;
case 4:
color = "yellow";
break;
case 5:
color = "orange";
break;
}
return (
<View style={{height: 100, backgroundColor: color, alignItems: 'center', justifyContent: 'center'}}>
<Text style={{fontSize: 32}}>{index}</Text>
</View>
);
};

/**
* This method is called whenever a view has to be stuck as a header or footer.
* Override the views for whichever sticky view requires changes.
* Eg. This can be used to add shadows etc. to the views once they stick.
*/
_overrideRowRenderer = (type, data, index) => {
const view = this._rowRenderer(type, data, index);
switch(index) {
case 7: // Only overriding sticky index 7, sticky indices 3 and 10 will remain as they are.
const color = "cyan";
return (
<View style={{height: 100, backgroundColor: color, alignItems: 'center', justifyContent: 'center'}}>
<Text style={{fontSize: 32}}>Overridden sticky</Text>
</View>
);
break;
}
return view;
};

render() {
return (
<StickyContainer stickyHeaderIndices={[3, 7, 10]}
stickyFooterIndices={[3, 7, 10]}
overrideRowRenderer={this._overrideRowRenderer}>
<RecyclerListView layoutProvider={this.layoutProvider}
ref={this._setRef}
dataProvider={this.dataProvider} rowRenderer={this._rowRenderer} showsVerticalScrollIndicator={false}/>
</StickyContainer>
);
}

_setRef(recycler) {
this._recyclerRef = recycler;
}
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "recyclerlistview",
"version": "1.4.0-beta.15",
"version": "2.0.0-beta.2",
"description": "The listview that you need and deserve. It was built for performance, uses cell recycling to achieve smooth scrolling.",
"main": "dist/reactnative/index.js",
"types": "dist/reactnative/index.d.ts",
Expand Down
57 changes: 38 additions & 19 deletions src/core/RecyclerListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
* DONE: Add Initial render Index support
* DONE: Add animated scroll to web scrollviewer
* DONE: Animate list view transition, including add/remove
* DONE: Implement sticky headers and footers
* TODO: Destroy less frequently used items in recycle pool, this will help in case of too many types.
* TODO: Implement sticky headers
* TODO: Make viewability callbacks configurable
* TODO: Observe size changes on web to optimize for reflowability
* TODO: Solve //TSI
Expand All @@ -36,13 +36,11 @@ import { TOnItemStatusChanged } from "./ViewabilityTracker";
import VirtualRenderer, { RenderStack, RenderStackItem, RenderStackParams } from "./VirtualRenderer";
import ItemAnimator, { BaseItemAnimator } from "./ItemAnimator";
import { DebugHandlers } from "..";

//#if [REACT-NATIVE]
import ScrollComponent from "../platform/reactnative/scrollcomponent/ScrollComponent";
import ViewRenderer from "../platform/reactnative/viewrenderer/ViewRenderer";
import { DefaultJSItemAnimator as DefaultItemAnimator } from "../platform/reactnative/itemanimators/defaultjsanimator/DefaultJSItemAnimator";
import { Platform } from "react-native";

const IS_WEB = !Platform || Platform.OS === "web";
//#endif

Expand Down Expand Up @@ -84,7 +82,7 @@ export interface OnRecreateParams {
export interface RecyclerListViewProps {
layoutProvider: BaseLayoutProvider;
dataProvider: DataProvider;
rowRenderer: (type: string | number, data: any, index: number) => JSX.Element | JSX.Element[] | null;
rowRenderer: (type: string | number, data: any, index: number, extendedState?: object) => JSX.Element | JSX.Element[] | null;
contextProvider?: ContextProvider;
renderAheadOffset?: number;
isHorizontal?: boolean;
Expand All @@ -93,6 +91,7 @@ export interface RecyclerListViewProps {
onEndReached?: () => void;
onEndReachedThreshold?: number;
onVisibleIndexesChanged?: TOnItemStatusChanged;
onVisibleIndicesChanged?: TOnItemStatusChanged;
renderFooter?: () => JSX.Element | JSX.Element[] | null;
externalScrollView?: { new(props: ScrollViewDefaultProps): BaseScrollView };
initialOffset?: number;
Expand Down Expand Up @@ -167,10 +166,14 @@ export default class RecyclerListView<P extends RecyclerListViewProps, S extends
public componentWillReceiveProps(newProps: RecyclerListViewProps): void {
this._assertDependencyPresence(newProps);
this._checkAndChangeLayouts(newProps);
if (!this.props.onVisibleIndexesChanged) {
if (!this.props.onVisibleIndicesChanged) {
this._virtualRenderer.removeVisibleItemsListener();
} else {
this._virtualRenderer.attachVisibleItemsListener(this.props.onVisibleIndexesChanged!);
}
if (this.props.onVisibleIndexesChanged) {
throw new CustomError(RecyclerListViewExceptions.usingOldVisibleIndexesChangedParam);
}
if (this.props.onVisibleIndicesChanged) {
this._virtualRenderer.attachVisibleItemsListener(this.props.onVisibleIndicesChanged!);
}
}

Expand Down Expand Up @@ -313,6 +316,14 @@ export default class RecyclerListView<P extends RecyclerListViewProps, S extends
return viewabilityTracker ? viewabilityTracker.findFirstLogicallyVisibleIndex() : 0;
}

public getRenderedSize(): Dimension {
return this._layout;
}

public getContentDimension(): Dimension {
return this._virtualRenderer.getLayoutDimension();
}

public render(): JSX.Element {
//TODO:Talha
// const {
Expand All @@ -322,7 +333,7 @@ export default class RecyclerListView<P extends RecyclerListViewProps, S extends
// renderAheadOffset,
// onEndReached,
// onEndReachedThreshold,
// onVisibleIndexesChanged,
// onVisibleIndicesChanged,
// initialOffset,
// initialRenderIndex,
// disableRecycling,
Expand All @@ -332,6 +343,7 @@ export default class RecyclerListView<P extends RecyclerListViewProps, S extends
// rowRenderer,
// ...props,
// } = this.props;

return (
<ScrollComponent
ref={(scrollComponent) => this._scrollComponent = scrollComponent as BaseScrollComponent | null}
Expand All @@ -343,7 +355,6 @@ export default class RecyclerListView<P extends RecyclerListViewProps, S extends
contentWidth={this._initComplete ? this._virtualRenderer.getLayoutDimension().width : 0}>
{this._generateRenderStack()}
</ScrollComponent>

);
}

Expand Down Expand Up @@ -430,7 +441,10 @@ export default class RecyclerListView<P extends RecyclerListViewProps, S extends
private _initTrackers(): void {
this._assertDependencyPresence(this.props);
if (this.props.onVisibleIndexesChanged) {
this._virtualRenderer.attachVisibleItemsListener(this.props.onVisibleIndexesChanged!);
throw new CustomError(RecyclerListViewExceptions.usingOldVisibleIndexesChangedParam);
}
if (this.props.onVisibleIndicesChanged) {
this._virtualRenderer.attachVisibleItemsListener(this.props.onVisibleIndicesChanged!);
}
this._params = {
initialOffset: this._initialOffset ? this._initialOffset : this.props.initialOffset,
Expand Down Expand Up @@ -560,16 +574,18 @@ export default class RecyclerListView<P extends RecyclerListViewProps, S extends
private _processOnEndReached(): void {
if (this.props.onEndReached && this._virtualRenderer) {
const layout = this._virtualRenderer.getLayoutDimension();
const windowBound = this.props.isHorizontal ? layout.width - this._layout.width : layout.height - this._layout.height;
const viewabilityTracker = this._virtualRenderer.getViewabilityTracker();
const lastOffset = viewabilityTracker ? viewabilityTracker.getLastOffset() : 0;
if (windowBound - lastOffset <= Default.value<number>(this.props.onEndReachedThreshold, 0)) {
if (!this._onEndReachedCalled) {
this._onEndReachedCalled = true;
this.props.onEndReached();
if (viewabilityTracker) {
const windowBound = this.props.isHorizontal ? layout.width - this._layout.width : layout.height - this._layout.height;
const lastOffset = viewabilityTracker ? viewabilityTracker.getLastOffset() : 0;
if (windowBound - lastOffset <= Default.value<number>(this.props.onEndReachedThreshold, 0)) {
if (this.props.onEndReached && !this._onEndReachedCalled) {
this._onEndReachedCalled = true;
this.props.onEndReached();
}
} else {
this._onEndReachedCalled = false;
}
} else {
this._onEndReachedCalled = false;
}
}
}
Expand Down Expand Up @@ -617,9 +633,12 @@ RecyclerListView.propTypes = {
//Specify how many pixels in advance you onEndReached callback
onEndReachedThreshold: PropTypes.number,

//Provides visible index, helpful in sending impression events etc, onVisibleIndexesChanged(all, now, notNow)
//Deprecated. Please use onVisibleIndicesChanged instead.
onVisibleIndexesChanged: PropTypes.func,

//Provides visible index, helpful in sending impression events etc, onVisibleIndicesChanged(all, now, notNow)
onVisibleIndicesChanged: PropTypes.func,

//Provide this method if you want to render a footer. Helpful in showing a loader while doing incremental loads.
renderFooter: PropTypes.func,

Expand Down
Loading

0 comments on commit 2965168

Please sign in to comment.