If you want to get viewable items in the [FlatList], you had better take a look at the onViewableItemsChanged
prop. For example, suppose you have a video list, and you want automatically play the video when the video is appearing on the screen for a few seconds. In iOS, there is visibleCells in UITableView
to achieve this. In React Native, I am glad to tell you that FlatList
has a more powerful property, onViewableItemsChanged
. This article would help you better understand how to use the
onViewableItemsChanged` prop in the [FlatList], and how it works under the hood.
What is onViewableItemsChanged
onViewableItemsChanged
is a prop in VirtualizedList and FlatList. When you scroll a FlatList, the items showing on the FlatList change. Then, this function is called, telling you what current viewableItems
are and what changed
items are.
This function should be used together with viewabilityConfig. A specific onViewableItemsChanged
will be called when its corresponding ViewabilityConfig
‘s conditions are met.
Here is ViewabilityConfig1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28export type ViewabilityConfig = {
/**
* Minimum amount of time (in milliseconds) that an item must be physically viewable before the
* viewability callback will be fired. A high number means that scrolling through content without
* stopping will not mark the content as viewable.
*/
minimumViewTime?: number,
/**
* Percent of viewport that must be covered for a partially occluded item to count as
* "viewable", 0-100. Fully visible items are always considered viewable. A value of 0 means
* that a single pixel in the viewport makes the item viewable, and a value of 100 means that
* an item must be either entirely visible or cover the entire viewport to count as viewable.
*/
viewAreaCoveragePercentThreshold?: number,
/**
* Similar to `viewAreaPercentThreshold`, but considers the percent of the item that is visible,
* rather than the fraction of the viewable area it covers.
*/
itemVisiblePercentThreshold?: number,
/**
* Nothing is considered viewable until the user scrolls or `recordInteraction` is called after
* render.
*/
waitForInteraction?: boolean,
|};
Here is the type of onViewableItemsChanged
function:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 /**
* Called when the viewability of rows changes, as defined by the
* `viewabilityConfig` prop.
*/
onViewableItemsChanged?: ?(info: {
viewableItems: Array<ViewToken>,
changed: Array<ViewToken>,
...
}) => void,
export type ViewToken = {
item: any,
// The key of this item
key: string,
index: ?number,
// indicated whether this item is viewable or not
isViewable: boolean,
section?: any,
...
};
How to use it
Let’s look at two simple example.
1 | viewabilityConfig = { |
Besides, supposed you have to implement different logic for items with 60% viewable region and those with 75% viewable region. You can use viewabilityConfigCallbackPairs
, which contains an list of key/value objects, which define different viewability
configurations and onViewableItemsChanged
callbacks.
1 | <FlatList |
How does onViewableItemsChanged works
Viewable Region
The layout and viewable region information for VirtualizedList is stored in _scrollMetrics
object. Through the nativeEvent
in onScroll
callback, VirtualizedList gets these layout information.
1 | const timestamp = e.timeStamp; |
If it is a vertical VirtualizedList, the layout.layoutMeasurement.height
in the nativeEvent
is assigned to visibleLength
; which is the height of viewable region here. Also, in a vertical VirtualizedList, the layout.layoutMeasurement.height
is equal to viewportHeight.
Overview
Different viewabilityConfig
in one VirtualizedList
_viewabilityTuples
is an array inside VirtualizedList
to store ViewabilityHelper/onViewableItemsChanged
pairs. This array is initialized in the constructor
function.
1 | _viewabilityTuples: Array<ViewabilityHelperCallbackTuple> = []; |
If you define viewabilityConfigCallbackPairs, each viewabilityConfig
will be used to initialize a different ViewabilityHelper
object.
1 | if (this.props.viewabilityConfigCallbackPairs) { |
ViewabilityHelper
is a utility class for calculating viewable items based on the viewabilityConfig and metrics, like the scroll position and layout.
As I mentioned before, in a VirtualizedList
could has several ViewabilityHelper
objects in _viewabilityTuples
, containing different viewabilityConfig
to handle different viewability conditions. Let’s take a look at some important props in ViewabilityHelper
.
1 | class ViewabilityHelper { |
Items’ layout
In the overview graph, you can see a func _updateViewableItems
called in many scenarios. For example, it is called in onScroll
callback. Then, It calls viewabilityHelper.onUpdate
to find out the viewable items, which appear in the viewport for VirtualizedList.
1 | _updateViewableItems(data: any) { |
this._scrollMetrics.visibleLength
is used asviewportHeight
this._createViewToken
is used to construct aViewToken
object, which containsitem
data,index
,key
andisViewable
flag of theitem
.- this._getFrameMetrics is a function to get layout information of the item cell by index. The item layout is from
getItemLayout
prop ofVirtualizedList
orthis._frames
map.this._frames
stores the itemKey/itemLayout pairs.
1 | // this._frames stores the item cell layout info |
- By
this.state
, we know the range of the rendered items byfirst
andlast
value.VirtualizedList
updates these two values when the rendered items are changed.
1 | type State = { |
How to find out viewable items
In onUpdate
method, it calls computeViewableItems
to get viewableIndices
. viewableIndices
is an array of indexes of the viewable items. So, how does computeViewableItems
work?
How to get the indexes of viewable items
In computeViewableItems
in the ViewabilityHelper
class, it iterates items from ${first}
to ${last}
. If an item is viewable, it will be stored in an array named viewableIndices
.
1 | for (let idx = first; idx <= last; idx++) { |
From the code, we can see the top
and bottom
value is related to the screen coordinate. I drew a graph to show the relationship between metrics.offset
, scrollOffset
, metrics.length
, top
and bottom
, to help you better understand the above code.
What kind of item is viewable
An item is said to be viewable when it meets the following conditions
for longer than ${minimumViewTime}
milliseconds (after an interaction if waitForInteraction
is true):
the fraction of the item visible in the view area >=
itemVisiblePercentThreshold
.
When it comes to the fraction of the item visible in the view area, we need to care about
cases shown in the following graph. RN useMath.min(bottom, viewportHeight) - Math.max(top, 0)
to calculate the viewable length.Entirely visible on screen when the height of a item is bigger than the
viewportHeight
.
1 | function _isViewable( |
Timer and Schedule
In onUpdate
func in ViewabilityHelper, if we define minimumViewTime
value, the _onUpdateSync
is scheduled to be called. It is the handler of the timeout
.
1 | this._viewableIndices = viewableIndices; |
And, If after a few seconds, ${minimumViewTime}, if some items aren’t longer viewable, the _onUpdateSync func, filter out these indices that have gone out of viewport.
1 | // Filter out indices that have gone out of view after `minimumViewTime` |
In the above graph, at first, the _viewableIndices
is from 1 to 9. Then the user scrolls the VirtualizedList
and the _onUpdateSync
is triggered after minimumViewTime
. At this moment, the current _viewableIndices
is from 2 to 10. So the item indexed 1 is filtered out.
6. How to get changed items
Comparing with the last time when onViewableItemsChanged
is triggered, at this time to trigger onViewableItemsChanged
. Some viewable items will be out of the screen, some hidden items will become viewable. In _onUpdateSync
function, the preItems
map stores the information about previous visible items, the previous means last time when VirtualizedList
calls onViewableItemsChanged
. Now it has a nextItems
map, which stores the information about viewable items this time. Then it figures out the changed
items by comparing these two maps. Then, it calls onViewableItemsChanged
, passing viewableItems
and changed
items.
1 | _onUpdateSync( |
scan qr code and share this article