Scrolling a 'moving' FlatList causes it to stutter or block scrolling completely on Android
还没有人认领这个 Issue。
- 主要语言
- C++
- 星标
- 127k
- 派生
- 25.3k
- 平均合并
- 1 天 23 小时
- 30 天内合并 PR
- 4
描述
Description
When you create a FlatList that moves as it's being scrolled (change in offset for the FlatList is a change in top property of FlatList's style) it stutters heavily. This behavior is much less disruptive when movement is scaled down compared to scrolling and becomes app-breaking when upscaled. See provided videos:
- Multiplier: 1.0
mult1.webm - Mutliplier: 2.0
mult2.webm - Multiplier: 0.5
mult0.5.webm
It was originally an issue in react-native-reanimated but after digging into it I narrowed it down to react-native and rewrote the code to not use react-native-reanimated. It seems to had been in the code for a long time, since this posts that dates 3 years back is about the same thing.
I checked it on versions 0.72, 0.71 and 0.70. On both Fabric and Paper and the result is always the same (although it feels a bit like Fabric behaviour is worse).
iOS works properly on each version too.
It seems like there is a problem when calculating layout and touch events - that's what I conducted due to multiplier making it better or worse. After moving a FlatList twice the distance it scrolled and having the component moved React Native seems to 'think' that the finger is now further than it should be and lowers the offset? That's just my guess.
React Native Version
0.72.3
Output of npx react-native info
System:
OS: macOS 13.4.1
CPU: (10) arm64 Apple M2 Pro
Memory: 49.31 MB / 16.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 18.16.0
path: /var/folders/jg/m839qn593nn7w_h3n0r9k25c0000gn/T/yarn--1689593442476-0.9098900178237601/node
Yarn:
version: 1.22.19
path: /var/folders/jg/m839qn593nn7w_h3n0r9k25c0000gn/T/yarn--1689593442476-0.9098900178237601/yarn
npm:
version: 9.5.1
path: ~/.nvm/versions/node/v18.16.0/bin/npm
Watchman:
version: 2023.06.12.00
path: /opt/homebrew/bin/watchman
Managers:
CocoaPods:
version: 1.12.1
path: /Users/user/.rbenv/shims/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 22.4
- iOS 16.4
- macOS 13.3
- tvOS 16.4
- watchOS 9.4
Android SDK: Not Found
IDEs:
Android Studio: 2022.1 AI-221.6008.13.2211.9619390
Xcode:
version: 14.3.1/14E300c
path: /usr/bin/xcodebuild
Languages:
Java:
version: 11.0.19
path: /usr/bin/javac
Ruby:
version: 3.2.2
path: /Users/user/.rbenv/shims/ruby
npmPackages:
"@react-native-community/cli": Not Found
react:
installed: 18.2.0
wanted: 18.2.0
react-native:
installed: 0.72.3
wanted: 0.72.3
react-native-macos: Not Found
npmGlobalPackages:
"react-native": Not Found
Android:
hermesEnabled: true
newArchEnabled: true
iOS:
hermesEnabled: true
newArchEnabled: false
Steps to reproduce
Just need to run the provided code snippet.
Snack, code example, screenshot, or link to a repository
https://github.com/tjzel/ReactNativeMovingFlatList
import React from 'react';
import {Dimensions, FlatList, StyleSheet, Text, View} from 'react-native';
const {height: SCREEN_HEIGHT} = Dimensions.get('screen');
const data = Array.from({length: 30}, (_, index) => ({
id: `${index + 1}`,
text: `Item ${index + 1}`,
}));
const multiplier = 1.0;
export default function App() {
const [animatedVerticalScroll, setAnimatedVerticalScroll] = React.useState(0);
// used to detect stutters
const previousValue = React.useRef(0);
const listAnimatedStyle = {
top:
animatedVerticalScroll * multiplier > SCREEN_HEIGHT * 0.7
? 0
: SCREEN_HEIGHT * 0.7 - animatedVerticalScroll * multiplier,
};
const renderItem = ({item}) => (
<View style={styles.item}>
<Text>{item.text}</Text>
</View>
);
const scrollHandler = event => {
// logger to detect if there was a stutter
if (previousValue.current > event.nativeEvent.contentOffset.y) {
console.log(
`\nScrolling stuttered!\nPrevious offset was ${previousValue.current}\nCurrent offset is ${event.nativeEvent.contentOffset.y}\n`,
);
} else {
console.log(event.nativeEvent.contentOffset.y);
}
setAnimatedVerticalScroll(event.nativeEvent.contentOffset.y);
previousValue.current = event.nativeEvent.contentOffset.y;
};
return (
<View style={styles.container}>
<View style={styles.containerBehind}>
<Text>Multiplier: {multiplier}</Text>
</View>
<FlatList
data={data}
renderItem={renderItem}
style={[styles.listContainer, listAnimatedStyle]}
onScroll={scrollHandler}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
containerBehind: {
width: '100%',
height: '100%',
flex: 1,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center',
},
listContainer: {
top: 0,
left: 0,
position: 'absolute',
zIndex: 10,
width: '100%',
height: '100%',
backgroundColor: 'green',
},
item: {
height: 50,
},
});
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
调研方向
首先运行提供的 ReactNativeMovingFlatList 复现,并在列出的所有倍数下比较 Android 滚动与移动中的 FlatList。该 issue 没有指定任何 React Native 源文件或测试;当 FlatList 的 top 样式发生变化时,滚动仍保持流畅且不会被阻塞(包括在 Fabric 和 Paper 下),即表示完成。
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- android, react-native, typescript
- 领域
- mobile, performance
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 停滞
- 描述清晰度
- 需要澄清
- 新手友好度
- 35/100