From 13a72e0ccceb2db6aeacd03b4f429d200392c17b Mon Sep 17 00:00:00 2001 From: islandryu Date: Fri, 6 May 2022 09:30:15 -0700 Subject: [PATCH] Fix items disappear when zooming VirtualizedList (#33765) Summary: fix https://github.com/facebook/react-native/issues/33705 Fixed the disappearance of items when scrolling after zooming VirtualizedList. example https://github.com/islandryu/zoomVirtualizedList Before modification https://user-images.githubusercontent.com/65934663/166849127-9fc3ba84-5172-4ae1-bd44-dd6312f283ec.mov After modification https://user-images.githubusercontent.com/65934663/166868632-2f78e118-f705-442d-b94e-ff165bed26c7.mov ## Changelog [General] [Fixed] - Fixed the disappearance of items when scrolling after zooming VirtualizedList. Pull Request resolved: https://github.com/facebook/react-native/pull/33765 Test Plan: Make the VirtualizedList zoomable with a prop such as maximumZoomScale. Apply the patch and make sure that items in the VirtualizedList do not disappear when you scroll after zooming the VirtualizedList. Or apply the patch from this repository and check it. https://github.com/islandryu/zoomVirtualizedList Reviewed By: javache Differential Revision: D36169686 Pulled By: yungsters fbshipit-source-id: 0f86255c2864be13f6d2dc5a58af1d11c9eedac3 --- Libraries/Lists/VirtualizeUtils.js | 10 +++++++--- Libraries/Lists/VirtualizedList.js | 5 +++++ Libraries/Lists/VirtualizedListContext.js | 1 + Libraries/Lists/__tests__/VirtualizeUtils-test.js | 12 ++++++------ Libraries/Lists/__tests__/VirtualizedList-test.js | 11 +++++++---- 5 files changed, 26 insertions(+), 13 deletions(-) diff --git a/Libraries/Lists/VirtualizeUtils.js b/Libraries/Lists/VirtualizeUtils.js index 6e50e62b9cabc9..85d50539db6200 100644 --- a/Libraries/Lists/VirtualizeUtils.js +++ b/Libraries/Lists/VirtualizeUtils.js @@ -25,12 +25,13 @@ export function elementsThatOverlapOffsets( offset: number, ... }, + zoomScale: number, ): Array { const out = []; let outLength = 0; for (let ii = 0; ii < itemCount; ii++) { const frame = getFrameMetrics(ii); - const trailingOffset = frame.offset + frame.length; + const trailingOffset = (frame.offset + frame.length) * zoomScale; for (let kk = 0; kk < offsets.length; kk++) { if (out[kk] == null && trailingOffset >= offsets[kk]) { out[kk] = ii; @@ -104,6 +105,7 @@ export function computeWindowedRenderLimits( offset: number, velocity: number, visibleLength: number, + zoomScale: number, ... }, ): { @@ -115,7 +117,7 @@ export function computeWindowedRenderLimits( if (itemCount === 0) { return prev; } - const {offset, velocity, visibleLength} = scrollMetrics; + const {offset, velocity, visibleLength, zoomScale} = 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 @@ -136,7 +138,8 @@ export function computeWindowedRenderLimits( ); const overscanEnd = Math.max(0, visibleEnd + leadFactor * overscanLength); - const lastItemOffset = getFrameMetricsApprox(itemCount - 1).offset; + const lastItemOffset = + getFrameMetricsApprox(itemCount - 1).offset * zoomScale; if (lastItemOffset < overscanBegin) { // Entire list is before our overscan window return { @@ -150,6 +153,7 @@ export function computeWindowedRenderLimits( [overscanBegin, visibleBegin, visibleEnd, overscanEnd], itemCount, getFrameMetricsApprox, + zoomScale, ); overscanFirst = overscanFirst == null ? 0 : overscanFirst; first = first == null ? Math.max(0, overscanFirst) : first; diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index 86f45354e36e20..fdf63b3198d962 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -1228,6 +1228,7 @@ class VirtualizedList extends React.PureComponent { timestamp: 0, velocity: 0, visibleLength: 0, + zoomScale: 1, }; _scrollRef: ?React.ElementRef = null; _sentEndForContentLength = 0; @@ -1624,6 +1625,9 @@ class VirtualizedList extends React.PureComponent { ); this._hasWarned.perf = true; } + + const zoomScale = e.nativeEvent.zoomScale; + this._scrollMetrics = { contentLength, dt, @@ -1632,6 +1636,7 @@ class VirtualizedList extends React.PureComponent { timestamp, velocity, visibleLength, + zoomScale, }; this._updateViewableItems(this.props.data); if (!this.props) { diff --git a/Libraries/Lists/VirtualizedListContext.js b/Libraries/Lists/VirtualizedListContext.js index d51a79244a0330..308f6a20d19a54 100644 --- a/Libraries/Lists/VirtualizedListContext.js +++ b/Libraries/Lists/VirtualizedListContext.js @@ -47,6 +47,7 @@ type Context = $ReadOnly<{ timestamp: number, velocity: number, visibleLength: number, + zoomScale: number, }, horizontal: ?boolean, getOutermostParentListRef: () => VirtualizedList, diff --git a/Libraries/Lists/__tests__/VirtualizeUtils-test.js b/Libraries/Lists/__tests__/VirtualizeUtils-test.js index 88eb4f068c46a8..c700d599100e93 100644 --- a/Libraries/Lists/__tests__/VirtualizeUtils-test.js +++ b/Libraries/Lists/__tests__/VirtualizeUtils-test.js @@ -48,9 +48,9 @@ describe('elementsThatOverlapOffsets', function () { offset: 100 * index, }; } - expect(elementsThatOverlapOffsets(offsets, 100, getFrameMetrics)).toEqual([ - 0, 2, 3, 4, - ]); + expect( + elementsThatOverlapOffsets(offsets, 100, getFrameMetrics, 1), + ).toEqual([0, 2, 3, 4]); }); it('handles variable length', function () { const offsets = [150, 250, 900]; @@ -62,7 +62,7 @@ describe('elementsThatOverlapOffsets', function () { {offset: 950, length: 150}, ]; expect( - elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii]), + elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii], 1), ).toEqual([1, 1, 3]); }); it('handles out of bounds', function () { @@ -73,7 +73,7 @@ describe('elementsThatOverlapOffsets', function () { {offset: 250, length: 100}, ]; expect( - elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii]), + elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii], 1), ).toEqual([1]); }); it('errors on non-increasing offsets', function () { @@ -84,7 +84,7 @@ describe('elementsThatOverlapOffsets', function () { {offset: 250, length: 100}, ]; expect(() => { - elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii]); + elementsThatOverlapOffsets(offsets, frames.length, ii => frames[ii], 1); }).toThrowErrorMatchingSnapshot(); }); }); diff --git a/Libraries/Lists/__tests__/VirtualizedList-test.js b/Libraries/Lists/__tests__/VirtualizedList-test.js index 9f902f180c7fbc..5354df93f7f7b5 100644 --- a/Libraries/Lists/__tests__/VirtualizedList-test.js +++ b/Libraries/Lists/__tests__/VirtualizedList-test.js @@ -384,7 +384,7 @@ describe('VirtualizedList', () => { const instance = component.getInstance(); - instance._onLayout({nativeEvent: {layout}}); + instance._onLayout({nativeEvent: {layout, zoomScale: 1}}); const initialContentHeight = props.initialNumToRender * ITEM_HEIGHT; @@ -1490,7 +1490,7 @@ it('calls _onCellLayout properly', () => { ); const cell = virtualList._cellRefs.i4; const event = { - nativeEvent: {layout: {x: 0, y: 0, width: 50, height: 50}}, + nativeEvent: {layout: {x: 0, y: 0, width: 50, height: 50}, zoomScale: 1}, }; cell._onLayout(event); expect(mock).toHaveBeenCalledWith(event, 'i4', 3); @@ -1544,7 +1544,9 @@ function simulateLayout(component, args) { function simulateViewportLayout(component, dimensions) { lastViewportLayout = dimensions; - component.getInstance()._onLayout({nativeEvent: {layout: dimensions}}); + component + .getInstance() + ._onLayout({nativeEvent: {layout: dimensions}, zoomScale: 1}); } function simulateContentLayout(component, dimensions) { @@ -1558,7 +1560,7 @@ function simulateCellLayout(component, items, itemIndex, dimensions) { const instance = component.getInstance(); const cellKey = instance._keyExtractor(items[itemIndex], itemIndex); instance._onCellLayout( - {nativeEvent: {layout: dimensions}}, + {nativeEvent: {layout: dimensions, zoomScale: 1}}, cellKey, itemIndex, ); @@ -1570,6 +1572,7 @@ function simulateScroll(component, position) { contentOffset: position, contentSize: lastContentLayout, layoutMeasurement: lastViewportLayout, + zoomScale: 1, }, }); }