Вопрос или проблема
Жесты касания не работают в Expo Go, но функционируют на вебе: как это исправить
Когда я открываю приложение в вебе с помощью команды npx expo start –tunnel, я могу проводить и переворачивать карты, но это не работает в expo go. Я вижу карты, но они не двигаются. Как будто сенсорный экран вообще не работает. Как я могу это исправить?
package.json
{
"name": "word-cards",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"@expo/metro-runtime": "~3.2.3",
"@react-native-async-storage/async-storage": "1.23.1",
"axios": "^1.7.7",
"expo": "^51.0.0",
"expo-constants": "~16.0.1",
"expo-font": "^12.0.10",
"expo-linking": "~6.3.1",
"expo-router": "~3.5.9",
"expo-splash-screen": "~0.27.4",
"expo-status-bar": "~1.12.1",
"npm-upgrade": "^3.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.74.5",
"react-native-deck-swiper": "^2.0.17",
"react-native-dotenv": "^3.4.11",
"react-native-flip-card": "^3.5.7",
"react-native-gesture-handler": "~2.16.1",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1",
"react-native-swipe-gestures": "^1.0.5",
"react-native-web": "~0.19.10"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}
index.js
import React, { useState, useRef, useCallback, useEffect } from 'react';
import { View, Text, StyleSheet, Dimensions, TouchableWithoutFeedback, Animated } from 'react-native';
import Swiper from 'react-native-deck-swiper';
import AsyncStorage from '@react-native-async-storage/async-storage';
const { width, height } = Dimensions.get('window');
const flashcards = [
{ id: 1, german: 'Ja', english: 'Yes' },
{ id: 2, german: 'Nein', english: 'No' },
{ id: 3, german: 'Bitte', english: 'Please' },
];
const shuffleArray = (array) => {
return array.sort(() => Math.random() - 0.5);
};
export default function App() {
const [currentIndex, setCurrentIndex] = useState(0);
const [cards, setCards] = useState(shuffleArray([...flashcards]));
const [label, setLabel] = useState('');
const [cardFrequency, setCardFrequency] = useState({});
const flipAnimation = useRef(new Animated.Value(0)).current;
const swiperRef = useRef(null);
useEffect(() => {
const loadCardData = async () => {
try {
const storedFrequency = await AsyncStorage.getItem('cardFrequency');
if (storedFrequency) {
setCardFrequency(JSON.parse(storedFrequency));
}
} catch (error) {
console.log("Ошибка при загрузке данных карты", error);
}
};
loadCardData();
}, []);
const flipCard = useCallback(() => {
Animated.spring(flipAnimation, {
toValue: flipAnimation._value === 0 ? 180 : 0,
friction: 8,
tension: 10,
useNativeDriver: true,
}).start();
}, [flipAnimation]);
const frontInterpolate = flipAnimation.interpolate({
inputRange: [0, 180],
outputRange: ['0deg', '180deg'],
});
const backInterpolate = flipAnimation.interpolate({
inputRange: [0, 180],
outputRange: ['180deg', '360deg'],
});
const frontAnimatedStyle = {
transform: [{ rotateY: frontInterpolate }],
};
const backAnimatedStyle = {
transform: [{ rotateY: backInterpolate }],
};
const updateCardOrder = () => {
const newCards = [...flashcards].sort((a, b) => {
const freqA = cardFrequency[a.id] || 1;
const freqB = cardFrequency[b.id] || 1;
return Math.random() * (1 / freqA) - Math.random() * (1 / freqB);
});
setCards(newCards);
};
const handleSwiping = useCallback((x) => {
if (x > 0) {
setLabel('TRUE');
} else if (x < 0) {
setLabel('FALSE');
} else {
setLabel('');
}
}, []);
const handleSwiped = useCallback((cardIndex) => {
const card = cards[cardIndex % cards.length];
const newFrequency = { ...cardFrequency };
if (label === 'TRUE') {
newFrequency[card.id] = (newFrequency[card.id] || 1) * 0.5;
} else if (label === 'FALSE') {
newFrequency[card.id] = (newFrequency[card.id] || 1) * 2;
}
setCardFrequency(newFrequency);
AsyncStorage.setItem('cardFrequency', JSON.stringify(newFrequency));
setCurrentIndex((prevIndex) => (prevIndex + 1) % flashcards.length);
setLabel('');
flipAnimation.setValue(0);
updateCardOrder();
}, [cardFrequency, cards, label, flipAnimation]);
const handleSwipedAll = useCallback(() => {
if (swiperRef.current) {
swiperRef.current.jumpToCardIndex(0);
}
setCurrentIndex(0);
flipAnimation.setValue(0);
setLabel('');
}, []);
return (
<View style={styles.container}>
<Swiper
ref={swiperRef}
cards={cards}
renderCard={(card) => (
<TouchableWithoutFeedback onPress={flipCard}>
<View style={styles.cardContainer}>
<Animated.View style={[styles.card, frontAnimatedStyle]}>
<Text style={styles.text}>{card.german}</Text>
</Animated.View>
<Animated.View style={[styles.card, styles.cardBack, backAnimatedStyle]}>
<Text style={styles.text}>{card.turkish}</Text>
</Animated.View>
{label !== '' && (
<View style={label === 'TRUE' ? styles.correctLabel : styles.wrongLabel}>
<Text style={styles.labelText}>{label}</Text>
</View>
)}
</View>
</TouchableWithoutFeedback>
)}
onSwiped={handleSwiped}
onSwiping={handleSwiping}
cardIndex={currentIndex}
backgroundColor={'#F5FCFF'}
stackSize={2}
showSecondCard={false}
animateCardOpacity
swipeBackCard
containerStyle={styles.swiperContainer}
cardVerticalMargin={80}
onSwipedAll={handleSwipedAll}
infinite
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
justifyContent: 'center',
alignItems: 'center',
},
swiperContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
cardContainer: {
width: width * 0.8,
height: height * 0.6,
},
card: {
width: '100%',
height: '100%',
borderRadius: 8,
borderWidth: 2,
borderColor: '#E8E8E8',
justifyContent: 'center',
backgroundColor: 'white',
backfaceVisibility: 'hidden',
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5,
},
cardBack: {
position: 'absolute',
top: 0,
},
text: {
textAlign: 'center',
fontSize: 24,
fontWeight: 'bold',
},
correctLabel: {
position: 'absolute',
top: 20,
left: 20,
backgroundColor: 'green',
padding: 10,
borderRadius: 5,
},
wrongLabel: {
position: 'absolute',
top: 20,
right: 20,
backgroundColor: 'red',
padding: 10,
borderRadius: 5,
},
labelText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
});
Думая, что это может быть проблема с моим телефоном, я хотел запустить его с Expo Snack, но не смог попробовать, потому что не смог установить пакеты.
Ответ или решение
Чтобы решить проблему с отсутствием сенсорных жестов в Expo Go, которые нормально работают на веб-версии вашего приложения, выполните следующие рекомендации:
1. Убедитесь, что библиотеки правильно установлены
Проверьте, что у вас установлены все необходимые зависимости для работы жестов. В вашем package.json
уже присутствует react-native-gesture-handler
и react-native-reanimated
, однако важно их правильно подключить и использовать.
2. Подключите Gesture Handler
Убедитесь, что вы импортируете и обернули ваше приложение в GestureHandlerRootView
. Это критически важно для работы жестов на мобильных устройствах. В index.js
сделайте следующее:
import 'react-native-gesture-handler'; // Импортируйте в самом начале
import { GestureHandlerRootView } from 'react-native-gesture-handler'; // Импортируйте GestureHandlerRootView
export default function App() {
// ваш код...
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.container}>
{/* Вставьте ваш компонент Swiper тут */}
</View>
</GestureHandlerRootView>
);
}
3. Проверьте конфигурации анимации
Убедитесь, что вы используете библиотеку react-native-reanimated
корректно. Некоторые версии могут требовать специальные настройки. Например, если используете react-native-reanimated
версии 2 и выше, добавьте следующее в вашу babel.config.js
:
module.exports = {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
4. Используйте useNativeDriver
Поскольку вы используете анимацию в своем приложении, убедитесь, что вы используете useNativeDriver: true
в Animated.spring()
. Это позволит анимациям работать более плавно и эффективно:
Animated.spring(flipAnimation, {
toValue: flipAnimation._value === 0 ? 180 : 0,
friction: 8,
tension: 10,
useNativeDriver: true, // уже установлено правильно
}).start();
5. Изменение на Expo Go
Иногда проблемы могут быть вызваны кэшированием в Expo Go. Попробуйте обновить Expo Go на вашем устройстве и сбросить кэш. Вы можете сделать это следующим образом:
- Убедитесь, что у вас последняя версия Expo Go.
- Запустите команду
npx expo start -c
для сброса кэша.
6. Установите чёткие размеры для компонентов
Для корректной работы свайпов иногда необходимо установить четкие размеры для карточек. Вы можете проверить стили вашего компонента в методе renderCard
. Убедитесь, что ширина и высота карточек чётко установлены.
7. Проверьте устройство
Если проблема сохраняется, воспользуйтесь другим устройством, чтобы исключить возможность аппаратного сбоя.
Заключение
Следуя этим рекомендациям, вы сможете исправить проблему с жестами в вашем приложении Expo Go. Если после выполнения всех шагов проблема не решится, попробуйте создать новый проект по примеру вашего и постепенно добавлять функциональность, чтобы изолировать источник проблемы.