我有一个 React Native 应用程序,其中包含一个用户可以滚动浏览的图表。在本示例中,我仅使用水平行的圆圈。
为了绘制图表,我使用 Skia (https://shopify.github.io/react-native-skia/)
通常,我会创建一个足够大以包含整个图形的 Canvas 对象,并将其放入 ScrollView 中。
<ScrollView horizontal={true}>
<Canvas style={{ width:5000, height:200 }}>
<GetCircles />
</Canvas>
</ScrollView>
问题是这个画布需要非常大,因为用户将能够滚动几个月的数据。因此,
width
属性可能需要太大,以至于创建的底层 OpenGL 纹理的大小对于硬件或 OpenGL 实现来说太大(或者我根据在其他环境(例如 Xamarin.Forms)中使用 Skia 的经验假设是这样).
在我的测试中,将宽度设置为 6300 左右会导致应用程序立即崩溃。
为了解决这个问题,我的想法是在 Skia Canvas 上覆盖一个 ScrollView。 ScrollView 可以包含一个大的 View 对象来匹配我打算模拟的大小,并且每当 ScrollView 滚动(其
onScroll
事件被触发)时,我都会设置一个状态,最终导致 Skia Canvas 被绘制为翻译,如以下片段所示:
const [scrollOffset, setScrollOffset] = useState(0);
function handleScroll(event: NativeSyntheticEvent<NativeScrollEvent>): void {
setScrollOffset(event.nativeEvent.contentOffset.x);
}
...
<Canvas style={{ width:800, height:200 }}>
<Group transform={[{translateX:-scrollOffset}]}>
<GetCircles />
</Group>
</Canvas>
<ScrollView horizontal={true} style={{height:100}} onScroll={handleScroll}>
{/* These are added just for the sake of the example: */}
<View style={{width:150, backgroundColor:"red"}} ></View>
<View style={{width:150, backgroundColor:"blue"}} ></View>
<View style={{width:150, backgroundColor:"red"}} ></View>
<View style={{width:150, backgroundColor:"blue"}} ></View>
<View style={{width:150, backgroundColor:"red"}} ></View>
<View style={{width:150, backgroundColor:"blue"}} ></View>
<View style={{width:150, backgroundColor:"red"}} ></View>
<View style={{width:150, backgroundColor:"blue"}} ></View>
</ScrollView>
此方法的工作原理是让 Skia Canvas 根据滚动视图更新其变换,但正如您在下面的 GIF 中所看到的,存在一些相当严重的滞后。我已经在实际硬件上对此进行了测试,所以我知道问题不是来自我的屏幕镜像/录制。
如何解决这个问题?是否有一些替代方法可以在 React Native 中的 Skia 中渲染非常大的可滚动图形,并且不会导致崩溃或滞后?
您可以使用 React Native Reanimated 和 React Native Gesture Handler,而不是滚动视图。
onScroll
处理程序在JS线程上运行,这使得动画变得滞后。
如果您用
GestureDetector
包裹画布,并使用手势更新共享的重新激活值,然后将其传递给组,则应该修复滞后。使用共享值时,它们不是从处理程序通过 UI 线程返回到 UI 组件,而是直接传递给重新激活的组件。
要使用动画值对组变换进行动画处理,应使用
createAnimatedComponent
对其进行包装
const AnimatedGroup = Animated.createAnimatedComponent(Group)
const changeX = useSharedValue( 0 )
const pan = Gesture.Pan().onChange( ( e ) => {
changeX.value = e.changeX
} )
...
<GestureDetector gesture={ pan }>
<Canvas style={{ width:800, height:200 }}>
<AnimatedGroup transform={[{translateX: changeX }]}>
<GetCircles />
</AnimatedGroup>
</Canvas>
</GestureDetector>
希望这有帮助!