如何在React Native中实现按钮左右滑动切换动画?
React Native 按钮滑动切换动画实现方案
需求说明
实现Ebook与Audiobook按钮切换时,右侧按钮向左滑动、左侧按钮向右滑动的平滑过渡动画,按钮样式如下:

实现方案
使用React Native自带的Animated库实现平移动画,重构状态管理逻辑让动画触发更简洁,以下是修改后的完整代码:
import React, { useEffect, useState, useRef } from 'react'; import { Dimensions, Pressable, SafeAreaView, StyleSheet, Text, View, Animated, } from 'react-native'; import { Book } from '../types'; import BooksList from '../json/books.json'; import AudiosList from '../json/audios.json'; import Menu from 'react-native-vector-icons/Entypo'; import Glass from 'react-native-vector-icons/Entypo'; import { ScrollView } from 'react-native-gesture-handler'; import AudioBox from '../components/AudiosBooksBox'; export default function HomeScreen() { const [books, setBooks] = useState<Book[]>([]); const [audios, setAudios] = useState<Book[]>([]); // 用单个字符串标记激活标签,替代两个独立布尔值 const [activeTab, setActiveTab] = useState<'ebook' | 'audiobook'>('ebook'); // 动画驱动值:0对应Ebook激活,1对应Audiobook激活 const slideAnim = useRef(new Animated.Value(0)).current; useEffect(() => { setBooks(BooksList); setAudios(AudiosList); }, []); // 切换标签时触发平滑动画 const switchTab = (tab: 'ebook' | 'audiobook') => { if (tab === activeTab) return; setActiveTab(tab); Animated.timing(slideAnim, { toValue: tab === 'audiobook' ? 1 : 0, duration: 300, useNativeDriver: true, }).start(); }; // 计算按钮平移距离:基于动画值生成插值 const buttonTranslateX = slideAnim.interpolate({ inputRange: [0, 1], outputRange: [0, -10], // 右侧按钮左滑距离 }); const reverseButtonTranslateX = slideAnim.interpolate({ inputRange: [0, 1], outputRange: [0, 10], // 左侧按钮右滑距离 }); const grand = Dimensions.get('window').width / 2; return ( <ScrollView style={styles.scrollContainer}> <View style={styles.searchContainer}> <Menu name={'menu'} size={30} color={'#1C0D45'} /> <Text style={styles.textSearch}>All Books</Text> <Glass name={'magnifying-glass'} size={30} color={'#1C0D45'} /> </View> <View style={styles.AudioOrEbookContainer}> {/* 左侧Ebook按钮:切换到Audiobook时右滑 */} <Animated.View style={{ transform: [{ translateX: activeTab === 'audiobook' ? reverseButtonTranslateX : 0 }] }}> <Pressable onPress={() => switchTab('ebook')} style={activeTab === 'ebook' ? styles.btn2 : styles.btn} > <Text style={styles.AorBText}>Ebook</Text> </Pressable> </Animated.View> {/* 右侧Audiobook按钮:切换到Ebook时左滑 */} <Animated.View style={{ transform: [{ translateX: activeTab === 'ebook' ? buttonTranslateX : 0 }] }}> <Pressable onPress={() => switchTab('audiobook')} style={activeTab === 'audiobook' ? styles.btn2 : styles.btn} > <Text style={styles.AorBText}>Audiobook</Text> </Pressable> </Animated.View> </View> <SafeAreaView style={styles.container}> {activeTab === 'ebook' && ( <View style={{ flexDirection: 'row' }}> <View> {books .filter((_, i) => i % 2 === 0) .map(book => ( <AudioBox width={grand} key={book.id} aspectRatio={book.aspectRatio} title={book.title} author={book.author} imageLink={book.imageLink} country={book.country} overview={book.overview} vote={book.vote} duration={book.duration} actor={book.actor} year={book.year} pages={book.pages} link={book.link} language={book.language} /> ))} </View> <View> {books .filter((_, i) => i % 2 !== 0) .map(book => ( <AudioBox width={grand} key={book.id} aspectRatio={book.aspectRatio} title={book.title} author={book.author} imageLink={book.imageLink} country={book.country} overview={book.overview} vote={book.vote} duration={book.duration} actor={book.actor} year={book.year} pages={book.pages} link={book.link} language={book.language} /> ))} </View> </View> )} {activeTab === 'audiobook' && ( <View style={{ flexDirection: 'row' }}> <View> {audios .filter((_, i) => i % 2 === 0) .map(audio => ( <AudioBox width={grand} key={audio.id} aspectRatio={audio.aspectRatio} title={audio.title} author={audio.author} imageLink={audio.imageLink} country={audio.country} overview={audio.overview} vote={audio.vote} duration={audio.duration} actor={audio.actor} year={audio.year} pages={audio.pages} link={audio.link} language={audio.language} /> ))} </View> <View> {audios .filter((_, i) => i % 2 !== 0) .map(audio => ( <AudioBox width={grand} key={audio.id} aspectRatio={audio.aspectRatio} title={audio.title} author={audio.author} imageLink={audio.imageLink} country={audio.country} overview={audio.overview} vote={audio.vote} duration={audio.duration} actor={audio.actor} year={audio.year} pages={audio.pages} link={audio.link} language={audio.language} /> ))} </View> </View> )} </SafeAreaView> </ScrollView> ); } const styles = StyleSheet.create({ scrollContainer: { backgroundColor: 'white', }, container: { marginTop: 50, flexDirection: 'row', justifyContent: 'center', backgroundColor: 'white', }, searchContainer: { marginTop: 100, flexDirection: 'row', justifyContent: 'space-around', }, btn: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: 'lightgray', borderRadius: 10, height: 40, width: '47%', }, btn2: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', backgroundColor: 'white', borderRadius: 10, height: 40, width: '47%', }, AudioOrEbookContainer: { marginTop: 50, flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', backgroundColor: 'lightgray', height: 50, width: '80%', marginLeft: 35, borderRadius: 10, }, AorBText: { color: '#1C0D45', fontWeight: '700', fontSize: 17, }, textSearch: { fontSize: 30, color: '#1C0D45', }, });
关键修改说明
- 状态重构:合并两个布尔状态为
activeTab字符串标记,简化切换逻辑。 - 动画驱动:用
Animated.Value控制动画进度,通过Animated.timing实现300ms平滑过渡。 - 平移插值:通过
interpolate将动画值转换为按钮平移距离,精准实现切换时的滑动效果。 - 原生动画:使用
Animated.View包裹按钮,绑定平移变换到transform属性,利用原生驱动保证动画流畅度。
内容的提问来源于stack exchange,提问作者greta calamari
相关产品推荐
相关产品推荐

