在項目中簡單使用了 react-native 的 Animated 動畫辆脸,這里介紹項目中使用到的兩種場景。
場景一:點擊關閉一個彈窗時,彈窗會慢慢變小到消失事镣,同時運動軌跡是慢慢從中心到標題欄的右側。該場景是在用戶關閉 APP 的功能介紹彈窗時揪胃,能夠讓用戶下次需要了解該功能時知道從哪里獲取信息璃哟。
場景二:點擊切換標簽頁時,被激活的標簽底部的橫線滑動的動畫效果喊递。這個功能很多插件都自帶這個功能随闪,不需要額外定義,我也是在使用 react-native-scrollable-tab-view 庫時添加了一些額外的標簽欄樣式時骚勘,需要自行添加改動畫铐伴。
Animated 動畫
Animated 動畫組件
Animated 中默認導出了以下這些可以直接使用的動畫組件:
- Animated.Image
- Animated.ScrollView
- Animated.Text
- Animated.View
我們還可以使用 createAnimatedComponent() 方法自定義動畫組件,下面示例中演示了創(chuàng)建一個可點擊的動畫組件:
import { TouchableWithoutFeedback, Animated } from 'react-native';
const AnimatedTouchableWithoutFeedback = Animated.createAnimatedComponent(TouchableWithoutFeedback);
// ...
<AnimatedTouchableWithoutFeedback onPress={() => {...}}>
<Animated.View style={{ opacity: this.state.fadeInOpacity }} />
</AnimatedTouchableWithoutFeedback>
兩種類型的值
Animated 提供了兩種類型的值:
- Animated.Value():用于單個值
- Animated.ValueXY():用于矢量值
配置動畫
Animated 提供了三種動畫類型俏讹。每種動畫類型都提供了特定的函數(shù)曲線当宴,用于控制動畫值從初始值變化到最終值的變化過程:
- Animated.timing():線性變化,使用 easing 函數(shù)讓數(shù)值隨時間動起來泽疆。
- Animated.decay():衰變效果户矢,以一個初始的速度和一個衰減系數(shù)逐漸減慢變?yōu)?。殉疼。
- Animated.spring():彈簧效果梯浪,提供了一個簡單的彈簧物理模型.
大多數(shù)情況下使用 timing()就可以了。默認情況下株依,它使用對稱的 easeInOut 曲線驱证,將對象逐漸加速到全速,然后通過逐漸減速停止結束恋腕。
組合動畫
動畫還可以使用組合函數(shù)以復雜的方式進行組合:
- Animated.delay():動畫延遲抹锄,在給定延遲后開始動畫。
- Animated.parallel():同時啟動多個動畫。默認情況下伙单,如果有任何一個動畫停止了获高,其余的也會被停止∥怯可以通過stopTogether 選項設置為 false 來取消這種關聯(lián)念秧。
- Animated.sequence():按順序啟動動畫,等待每一個動畫完成后再開始下一個動畫布疼。如果當前的動畫被中止摊趾,后面的動畫則不會繼續(xù)執(zhí)行。
- Animated.stagger():按照給定的延時間隔游两,順序并行的啟動動畫砾层。即在前一個動畫開始之后,隔一段指定時間開始執(zhí)行下一個動畫贱案,并不關心前一個動畫是否已經(jīng)完成肛炮,所以有可能會出現(xiàn)多個動畫同時執(zhí)行的情況。
使用示例
創(chuàng)建動畫最簡單的工作流程是創(chuàng)建一個 Animated.Value 宝踪,將它連接到動畫組件的一個或多個樣式屬性侨糟,然后使用 Animated.timing() 等動畫效果展示數(shù)據(jù)的變化,tart/stop 方法來控制基于時間的動畫執(zhí)行瘩燥。示例中透明度 new Animated.Value(0) 秕重,首先設為 0,后面 timing 動畫中 toValue: 1颤芬,即最終變?yōu)橥耆煌该鞅N覀円部梢远x一個漸隱的效果,從 1 變?yōu)?0站蝠,組件就會慢慢消失汰具。
import React from 'react';
import { Animated, Text, View } from 'react-native';
class FadeInView extends React.Component {
state = {
fadeInOpacity: new Animated.Value(0), // 透明度初始值設為0
}
componentDidMount() {
Animated.timing( // 隨時間變化而執(zhí)行動畫
this.state.fadeInOpacity, // 動畫中的變量值
{
toValue: 1, // 透明度最終變?yōu)?,即完全不透明
duration: 10000, // 讓動畫持續(xù)一段時間
}
).start(); // 開始執(zhí)行動畫
}
render() {
const { fadeInOpacity } = this.state;
return (
<Animated.View // 使用專門的可動畫化的View組件
style={{
...this.props.style,
opacity: fadeInOpacity, // 將透明度指定為動畫變量值
}}
>
{this.props.children}
</Animated.View>
);
}
}
場景一示例
class Comp1 extends React.Component<IProps, {}> {
constructor(props: IProps) {
super(props);
this.state = {
isModalVisible: false, // 彈窗是否可見
modalWidth: new Animated.Value(1), // 彈窗初始寬度
modalHeight: new Animated.Value(1), // 彈窗初始高度
};
}
startAnimated = () => {
// 同步執(zhí)行的動畫
Animated.parallel([
Animated.timing(this.state.modalHeight, {
toValue: 0,
duration: 500,
easing: Easing.linear,
}),
Animated.timing(this.state.modalWidth, {
toValue: 0,
duration: 500,
easing: Easing.linear,
}),
// 可以添加其他動畫
]).start(() => {
// 這里可以添加動畫之后要執(zhí)行的函數(shù)
setTimeout(() => {
this.setState({ isModalVisible: false });
}, 100);
});
};
render() {
const modalWidth = this.state.modalWidth.interpolate({
inputRange: [0, 1],
outputRange: [1, 300],
});
const modalHeight = this.state.modalHeight.interpolate({
inputRange: [0, 1],
outputRange: [1, 200],
});
return (
<View>
{/* 其他組件... */}
<Animated.View
style={[
styles.modalContent,
{
width: modalWidth,
height: modalHeight,
},
]}
>
<View style={{ // ... }}>
<Animated.Text style={[styles.title, { fontSize: titleFontSize }]}>標題</Animated.Text>
<ScrollView>
<Animated.Text style={[styles.content, { fontSize: contentFontSize }]}>這是提示文本菱魔,這是提示文本留荔,這是提示文本。</Animated.Text>
</ScrollView>
<Button onClick={() => {}}>
<Animated.Text style={{ fontSize: titleFontSize }}>跳轉(zhuǎn)</Animated.Text>
</Button>
</View>
<TouchableOpacity
style={{ marginTop: 20, padding: 10, alignItems: 'center' }}
onPress={this.startAnimated}
>
{/* 關閉按鈕 */}
</TouchableOpacity>
</Animated.View>
</View>
)
}
}
場景二示例
class Comp2 extends React.Component<IProps, {}> {
constructor(props: IProps) {
super(props);
this.state = {
lineWidth: new Animated.Value(0),
lineLeft: new Animated.Value(0),
prevWidth: 0,
prevLeft: 0,
nextWidth: 0,
nextLeft: 0,
};
}
componentDidUpdate() {
if (// ...) {
this.startAnimated();
this.setState((prevState: any, props: IProps) => ({
prevLeft: prevState.nextLeft,
prevWidth: prevState.nextWidth,
nextLeft: this.tabbarInfos[activeTab].left,
nextWidth: this.tabbarInfos[activeTab].width,
}));
}
}
startAnimated = () => {
this.state.lineWidth.setValue(0);
this.state.lineLeft.setValue(0);
Animated.parallel([
Animated.timing(this.state.lineWidth, {
toValue: 1,
duration: 200,
easing: Easing.linear,
}),
Animated.timing(this.state.lineLeft, {
toValue: 1,
duration: 200,
easing: Easing.linear,
}),
]).start();
};
render() {
const lineLeft = this.state.lineLeft.interpolate({
inputRange: [0, 1],
outputRange: [this.state.prevLeft, this.state.nextLeft],
});
const lineWidth = this.state.lineWidth.interpolate({
inputRange: [0, 1],
outputRange: [this.state.prevWidth, this.state.nextWidth],
});
const tabUnderlineStyle = {
position: 'absolute',
height: 1,
backgroundColor: '#666',
bottom: 0,
width: lineWidth,
left: lineLeft,
};
return (
<View>
{/* 其他組件... */}
<Animated.View style={[tabUnderlineStyle, { //... }]} />
</View>
)
}
}
參考文章