Skip to content

Commit

Permalink
cleanup and open source WindowedListView
Browse files Browse the repository at this point in the history
Summary:`WindowedListView` is designed for memory efficient scrolling of
huge/infinite lists of variable height rows. It works by measuring row heights
with `onLayout` and caching the results, then unmounting rows that scroll
offscreen, replacing them with an equivalent offset in the spacer view. Care is
taken to render a constant number of rows, and to only render one new row per
tick to improve framerate and app responsiveness. WLV is also compatible with
<Incremental> used within the rows themselves.

`WindowedListView` is not a drop-in replacement for `ListView` - it doesn't
support many of the features of `ListView`, such as section headers, only
accepts a simple array of data instead of a datasource, and doesn't support
horizontal scrolling. This may change in the future.

This is still experimental - we haven't deployed this for any production apps
yet.

Differential Revision: D2791402

fb-gh-sync-id: 5f104e0903f6ba586d2d651bdf82863a231279d8
fbshipit-source-id: 5f104e0903f6ba586d2d651bdf82863a231279d8
  • Loading branch information
sahrens authored and Facebook Github Bot 2 committed Apr 1, 2016
1 parent cb5d377 commit cd79e26
Show file tree
Hide file tree
Showing 3 changed files with 731 additions and 2 deletions.
7 changes: 5 additions & 2 deletions Libraries/Experimental/Incremental.js
Expand Up @@ -88,7 +88,6 @@ export type Props = {
*/
name: string;
children: any;
disabled: boolean;
};
class Incremental extends React.Component {
props: Props;
Expand All @@ -98,6 +97,7 @@ class Incremental extends React.Component {
context: Context;
_incrementId: number;
_mounted: boolean;
_rendered: boolean;

constructor(props: Props, context: Context) {
super(props, context);
Expand Down Expand Up @@ -135,8 +135,11 @@ class Incremental extends React.Component {
}

render(): ?ReactElement {
if (this.props.disabled || !this.context.incrementalGroupEnabled || this.state.doIncrementalRender) {
if (this._rendered || // Make sure that once we render once, we stay rendered even if incrementalGroupEnabled gets flipped.
!this.context.incrementalGroupEnabled ||
this.state.doIncrementalRender) {
DEBUG && console.log('render ' + this.getName());
this._rendered = true;
return this.props.children;
}
return null;
Expand Down
83 changes: 83 additions & 0 deletions Libraries/Experimental/ViewabilityHelper.js
@@ -0,0 +1,83 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ViewabilityHelper
* @flow
*/
'use strict';

/**
* A row is said to be in a "viewable" state when either of the following
* is true:
* - Occupying >= viewablePercentThreshold of the viewport
* - Entirely visible on screen
*/
const ViewabilityHelper = {
computeViewableRows(
viewablePercentThreshold: number,
rowFrames: Array<Object>,
scrollOffsetY: number,
viewportHeight: number
): Array<number> {
var viewableRows = [];
var firstVisible = -1;
for (var idx = 0; idx < rowFrames.length; idx++) {
var frame = rowFrames[idx];
if (!frame) {
continue;
}
var top = frame.y - scrollOffsetY;
var bottom = top + frame.height;
if ((top < viewportHeight) && (bottom > 0)) {
firstVisible = idx;
if (_isViewable(
viewablePercentThreshold,
top,
bottom,
viewportHeight
)) {
viewableRows.push(idx);
}
} else if (firstVisible >= 0) {
break;
}
}
return viewableRows;
},
};

function _isViewable(
viewablePercentThreshold: number,
top: number,
bottom: number,
viewportHeight: number
): bool {
return _isEntirelyVisible(top, bottom, viewportHeight) ||
_getPercentOccupied(top, bottom, viewportHeight) >=
viewablePercentThreshold;
}

function _getPercentOccupied(
top: number,
bottom: number,
viewportHeight: number
): number {
var visibleHeight = Math.min(bottom, viewportHeight) - Math.max(top, 0);
visibleHeight = Math.max(0, visibleHeight);
return Math.max(0, visibleHeight * 100 / viewportHeight);
}

function _isEntirelyVisible(
top: number,
bottom: number,
viewportHeight: number
): bool {
return top >= 0 && bottom <= viewportHeight && bottom > top;
}

module.exports = ViewabilityHelper;

1 comment on commit cd79e26

@hufeng
Copy link

@hufeng hufeng commented on cd79e26 Apr 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome

Please sign in to comment.