mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-05-12 03:35:30 +08:00
Move new components out of Experimental directory
Summary: I think these are sufficiently baked. Also beef up comments. Reviewed By: yungsters Differential Revision: D4632604 fbshipit-source-id: 64ae6b240a05d62e418099f7403e1781f9b4717c
This commit is contained in:
committed by
Facebook Github Bot
parent
5facc23799
commit
7b35eb3fdb
163
Libraries/CustomComponents/Lists/VirtualizeUtils.js
Normal file
163
Libraries/CustomComponents/Lists/VirtualizeUtils.js
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 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 VirtualizeUtils
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Used to find the indices of the frames that overlap the given offsets. Useful for finding the
|
||||
* items that bound different windows of content, such as the visible area or the buffered overscan
|
||||
* area.
|
||||
*/
|
||||
function elementsThatOverlapOffsets(
|
||||
offsets: Array<number>,
|
||||
itemCount: number,
|
||||
getFrameMetrics: (index: number) => {length: number, offset: number},
|
||||
): Array<number> {
|
||||
const out = [];
|
||||
for (let ii = 0; ii < itemCount; ii++) {
|
||||
const frame = getFrameMetrics(ii);
|
||||
const trailingOffset = frame.offset + frame.length;
|
||||
for (let kk = 0; kk < offsets.length; kk++) {
|
||||
if (out[kk] == null && trailingOffset >= offsets[kk]) {
|
||||
out[kk] = ii;
|
||||
if (kk === offsets.length - 1) {
|
||||
invariant(
|
||||
out.length === offsets.length,
|
||||
'bad offsets input, should be in increasing order ' + JSON.stringify(offsets)
|
||||
);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the number of elements in the `next` range that are new compared to the `prev` range.
|
||||
* Handy for calculating how many new items will be rendered when the render window changes so we
|
||||
* can restrict the number of new items render at once so that content can appear on the screen
|
||||
* faster.
|
||||
*/
|
||||
function newRangeCount(
|
||||
prev: {first: number, last: number},
|
||||
next: {first: number, last: number},
|
||||
): number {
|
||||
return (next.last - next.first + 1) -
|
||||
Math.max(
|
||||
0,
|
||||
1 + Math.min(next.last, prev.last) - Math.max(next.first, prev.first)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom logic for determining which items should be rendered given the current frame and scroll
|
||||
* metrics, as well as the previous render state. The algorithm may evolve over time, but generally
|
||||
* prioritizes the visible area first, then expands that with overscan regions ahead and behind,
|
||||
* biased in the direction of scroll.
|
||||
*/
|
||||
function computeWindowedRenderLimits(
|
||||
props: {
|
||||
data: any,
|
||||
getItemCount: (data: any) => number,
|
||||
maxToRenderPerBatch: number,
|
||||
windowSize: number,
|
||||
},
|
||||
prev: {first: number, last: number},
|
||||
getFrameMetricsApprox: (index: number) => {length: number, offset: number},
|
||||
scrollMetrics: {dt: number, offset: number, velocity: number, visibleLength: number},
|
||||
): {first: number, last: number} {
|
||||
const {data, getItemCount, maxToRenderPerBatch, windowSize} = props;
|
||||
const itemCount = getItemCount(data);
|
||||
if (itemCount === 0) {
|
||||
return prev;
|
||||
}
|
||||
const {offset, velocity, visibleLength} = scrollMetrics;
|
||||
|
||||
// Start with visible area, then compute maximum overscan region by expanding from there, biased
|
||||
// in the direction of scroll. Total overscan area is capped, which should cap memory consumption
|
||||
// too.
|
||||
const visibleBegin = Math.max(0, offset);
|
||||
const visibleEnd = visibleBegin + visibleLength;
|
||||
const overscanLength = (windowSize - 1) * visibleLength;
|
||||
const leadFactor = Math.max(0, Math.min(1, velocity / 5 + 0.5));
|
||||
const overscanBegin = Math.max(0, visibleBegin - (1 - leadFactor) * overscanLength);
|
||||
const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength);
|
||||
|
||||
// Find the indices that correspond to the items at the render boundaries we're targetting.
|
||||
let [overscanFirst, first, last, overscanLast] = elementsThatOverlapOffsets(
|
||||
[overscanBegin, visibleBegin, visibleEnd, overscanEnd],
|
||||
props.getItemCount(props.data),
|
||||
getFrameMetricsApprox,
|
||||
);
|
||||
overscanFirst = overscanFirst == null ? 0 : overscanFirst;
|
||||
first = first == null ? Math.max(0, overscanFirst) : first;
|
||||
overscanLast = overscanLast == null ? (itemCount - 1) : overscanLast;
|
||||
last = last == null ? Math.min(overscanLast, first + maxToRenderPerBatch - 1) : last;
|
||||
const visible = {first, last};
|
||||
|
||||
// We want to limit the number of new cells we're rendering per batch so that we can fill the
|
||||
// content on the screen quickly. If we rendered the entire overscan window at once, the user
|
||||
// could be staring at white space for a long time waiting for a bunch of offscreen content to
|
||||
// render.
|
||||
let newCellCount = newRangeCount(prev, visible);
|
||||
|
||||
while (true) {
|
||||
if (first <= overscanFirst && last >= overscanLast) {
|
||||
// If we fill the entire overscan range, we're done.
|
||||
break;
|
||||
}
|
||||
const maxNewCells = newCellCount >= maxToRenderPerBatch;
|
||||
const firstWillAddMore = first <= prev.first || first > prev.last;
|
||||
const firstShouldIncrement = first > overscanFirst && (!maxNewCells || !firstWillAddMore);
|
||||
const lastWillAddMore = last >= prev.last || last < prev.first;
|
||||
const lastShouldIncrement = last < overscanLast && (!maxNewCells || !lastWillAddMore);
|
||||
if (maxNewCells && !firstShouldIncrement && !lastShouldIncrement) {
|
||||
// We only want to stop if we've hit maxNewCells AND we cannot increment first or last
|
||||
// without rendering new items. This let's us preserve as many already rendered items as
|
||||
// possible, reducing render churn and keeping the rendered overscan range as large as
|
||||
// possible.
|
||||
break;
|
||||
}
|
||||
if (firstShouldIncrement) {
|
||||
if (firstWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
first--;
|
||||
}
|
||||
if (lastShouldIncrement) {
|
||||
if (lastWillAddMore) {
|
||||
newCellCount++;
|
||||
}
|
||||
last++;
|
||||
}
|
||||
}
|
||||
if (!(
|
||||
last >= first &&
|
||||
first >= 0 && last < itemCount &&
|
||||
first >= overscanFirst && last <= overscanLast &&
|
||||
first <= visible.first && last >= visible.last
|
||||
)) {
|
||||
throw new Error('Bad window calculation ' +
|
||||
JSON.stringify({first, last, itemCount, overscanFirst, overscanLast, visible}));
|
||||
}
|
||||
return {first, last};
|
||||
}
|
||||
|
||||
const VirtualizeUtils = {
|
||||
computeWindowedRenderLimits,
|
||||
elementsThatOverlapOffsets,
|
||||
newRangeCount,
|
||||
};
|
||||
|
||||
module.exports = VirtualizeUtils;
|
||||
Reference in New Issue
Block a user