如何对React Native Reanimated中的键盘事件做出反应?

时间:2019-10-16 17:09:48

标签: javascript performance react-native animation react-native-reanimated

我要做什么

我正在尝试创建一个动画的间距/填充元素,该元素在显示或隐藏键盘时会更改高度,以确保TextInput不会被键盘或避免使用KeyboardAvoidingView的键盘的按钮覆盖。如果按钮要覆盖输入,我只希望此动画空间更改高度,否则,我不希望间隔更改高度。这是设计要求。

我当前的解决方案

我以前可以使用Animated的{​​{1}} API来实现,但是我想使用react-native来获得在UI线程上运行所有内容的性能优势。我实际上有一个可行的解决方案,但是在动画过程中,UI线程会降至50 fps的中间水平,因此我假设自己做错了事。

您将在下面的代码中看到,我正在计算所有元素的高度,以确定锚定在键盘顶部的按钮是否与react-native-reanimated重叠。如果是这样,我从文本(TextInput)上方间距的高度中减去重叠量。您应该能够复制并粘贴此代码并运行它。如果打开探查器并观看UI线程,请通过聚焦输入并轻按return来关闭动画来切换动画。动画可以运行,但是会导致UI线程以60fps以下的速度运行。

可复制的代码

我用animHeaderHeight引导了项目。以下是软件包的版本:

expo init

这是代码。

"expo": "^35.0.0",
"expo-constants": "~7.0.0",
"react": "16.8.3",
"react-native": "https://github.com/expo/react-native/archive/sdk-35.0.0.tar.gz",
"react-native-gesture-handler": "~1.3.0",
"react-native-reanimated": "~1.2.0",

最终想法

我希望UI fps保持在60不变,但是我进行设置的方式会导致帧丢失。我想知道它是否与我的import React, { useEffect, useRef } from "react"; import { View, Text, TouchableOpacity, StyleSheet, Keyboard, KeyboardEvent, KeyboardEventName, Platform, TextInput, SafeAreaView, KeyboardAvoidingView, LayoutChangeEvent, Dimensions } from "react-native"; import Animated, { Easing } from "react-native-reanimated"; import Constants from "expo-constants"; const DEVICE_HEIGHT = Dimensions.get("screen").height; const STATUS_BAR_HEIGHT = Constants.statusBarHeight; const HEADER_HEIGHT = 100; const MAX_ANIMATED_HEIGHT = 75; const BOTTOM_BUTTON_HEIGHT = 60; const KEYBOARD_EASING = Easing.bezier(0.38, 0.7, 0.125, 1.0); const { Value, Clock, set, block, cond, eq, and, neq, add, sub, max, startClock, stopClock, timing, interpolate } = Animated; export default App = () => { // These refs are used so the height calculations are only called once and don't cause re-renders const wasKeyboardMeasured = useRef(false); const wasContentMeasured = useRef(false); const clock = new Clock(); const keyboardShown = new Value(-1); const animKeyboardHeight = new Value(0); const animContentHeight = new Value(0); function handleLayout(e) { if (!wasContentMeasured.current) { // Set animated value and set ref measured flag true const height = Math.floor(e.nativeEvent.layout.height); wasContentMeasured.current = true; animContentHeight.setValue(height); } } useEffect(() => { const handleKbdShow = (e: KeyboardEvent) => { if (!wasKeyboardMeasured.current) { // Set animated value and set ref measured flag true const kbdHeight = Math.floor(e.endCoordinates.height); wasKeyboardMeasured.current = true; animKeyboardHeight.setValue(kbdHeight); } keyboardShown.setValue(1); }; const handleKbdHide = () => { keyboardShown.setValue(0 ); }; const kbdWillOrDid = Platform.select({ ios: "Will", android: "Did" }); const showEventName = `keyboard${kbdWillOrDid}Show`; const hideEventName = `keyboard${kbdWillOrDid}Hide`; Keyboard.addListener(showEventName, handleKbdShow); Keyboard.addListener(hideEventName, handleKbdHide); return () => { Keyboard.removeListener(showEventName, handleKbdShow); Keyboard.removeListener(hideEventName, handleKbdHide); }; }, []); const animHeaderHeight = runTiming( clock, keyboardShown, animContentHeight, animKeyboardHeight ); return ( <SafeAreaView style={styles.container}> <KeyboardAvoidingView style={styles.container} behavior="padding"> <View style={styles.header}> <Text style={styles.headerText}>Header</Text> </View> <Animated.View style={[styles.animatedSpace, { height: animHeaderHeight }]} /> <View onLayout={handleLayout}> <View style={styles.heading}> <Text style={styles.headingText}> Note: CHANGE THIS TEXT CONTENT TO WHATEVER LENGTH MAKES THE BOTTOM BUTTON OVERLAP THE TEXT INPUT WHEN THE KEYBOARD IS SHOWN! Lorem ipsum dolor sit amet, consectetur adipiscing elit. </Text> </View> <View style={styles.textInputContainer}> <TextInput style={styles.textInput} autoFocus={true} /> </View> </View> <TouchableOpacity style={styles.bottomButton} /> </KeyboardAvoidingView> </SafeAreaView> ); }; function runTiming( clock, keyboardShown, animContentHeight, animKeyboardHeight ) { const state = { finished: new Value(0), position: new Value(0), time: new Value(0), frameTime: new Value(0) }; const config = { duration: 300, toValue: new Value(-1), easing: KEYBOARD_EASING }; const upperContentHeightNode = add( STATUS_BAR_HEIGHT, HEADER_HEIGHT, MAX_ANIMATED_HEIGHT, animContentHeight ); const keyboardContentHeightNode = add( BOTTOM_BUTTON_HEIGHT, animKeyboardHeight ); const overlap = max( sub(add(upperContentHeightNode, keyboardContentHeightNode), DEVICE_HEIGHT), 0 ); const headerMinHeightNode = max(sub(MAX_ANIMATED_HEIGHT, overlap), 0); return block([ cond(and(eq(keyboardShown, 1), neq(config.toValue, 1)), [ set(state.finished, 0), set(state.time, 0), set(state.frameTime, 0), set(config.toValue, 1), startClock(clock) ]), cond(and(eq(keyboardShown, 0), neq(config.toValue, 0)), [ set(state.finished, 0), set(state.time, 0), set(state.frameTime, 0), set(config.toValue, 0), startClock(clock) ]), timing(clock, state, config), cond(state.finished, stopClock(clock)), interpolate(state.position, { inputRange: [0, 1], outputRange: [MAX_ANIMATED_HEIGHT, headerMinHeightNode] }) ]); } // Coloring below is used just to easily see the different components const styles = StyleSheet.create({ container: { flex: 1 }, header: { height: HEADER_HEIGHT, width: "100%", backgroundColor: "teal", justifyContent: "center", alignItems: "center" }, headerText: { color: "white" }, heading: { alignItems: "center", marginBottom: 15, paddingHorizontal: 30 }, headingText: { fontSize: 28, fontWeight: "600", textAlign: "center" }, animatedSpace: { backgroundColor: "pink", width: "100%" }, textInputContainer: { alignItems: "center", paddingHorizontal: 40, width: "100%", height: 60 }, textInput: { backgroundColor: "lightgray", width: "100%", height: 60 }, bottomButton: { marginTop: "auto", height: BOTTOM_BUTTON_HEIGHT, backgroundColor: "orange", paddingHorizontal: 20 } }); 动画取决于键盘的状态(即取决于JS线程的信息)有关。我有点想知道如果不通过桥接器在JS和UI线程之间进行不间断的通信,是否甚至可以做到这一点。任何帮助或指示将不胜感激。

1 个答案:

答案 0 :(得分:0)

为了安全起见,您可以将runTiming调用包装到具有适当依赖项的用例中吗? [keyboardShown等]。您的代码段中有很多可能引发问题的副作用。