一鱼的、LayoutAnimation
主要用于視圖位置、透明度等state改變之前調(diào)用痘煤,讓其變化過程帶上動畫效果凑阶,不那么生硬。適用于全局變化衷快。常規(guī)用法就不說了宙橱,主要看下細節(jié)點。如下是一個典型的LayoutAnimation動畫
LayoutAnimation.configureNext({
duration:800,
create:{
type:LayoutAnimation.Types.spring,
property:LayoutAnimation.Properties.opacity,
},
update:{
type:LayoutAnimation.Types.linear,
},
delete: {
type:LayoutAnimation.Types.linear,
property:LayoutAnimation.Properties.opacity,
},
});
參數(shù)意義:
@type::決定動畫進度蘸拔,例如彈簧spring师郑,線性linear,easeInEaseOut,easeIn,easeOut,keyboard等
@property:opacity或scaleXY调窍,只在create和delete之下時生效宝冕,決定視圖出現(xiàn)\消失的樣式,opacity為改變透明度出現(xiàn)\消失邓萨,scaleXY則為正中心一個點地梨,然后放大至原型出現(xiàn)菊卷。在create和delete中此屬性為必需,update中無效湿刽,不必寫的烁。
@create: 適用于給從無到有剛剛創(chuàng)建出來視圖添加動畫褐耳,例如點擊按鈕诈闺,在列表后添加一張圖片,當property為opacity時铃芦,圖片呈現(xiàn)完全透明在逐漸出現(xiàn)動畫效果雅镊。
@delete:適用于視圖消失時的動畫效果,例如點擊按鈕刃滓,刪除列表最后一張圖片仁烹,當property為scaleXY時,圖片呈現(xiàn)中點不變逐漸等比例縮小至消失動畫效果咧虎。
一般情況下LayoutAnimation不必使用上面的自定義動畫過程卓缰,直接使用系統(tǒng)已經(jīng)寫好的那幾個動畫就可以了,如LayoutAnimation.easeInEaseOut()砰诵、LayoutAnimation.spring()
在iOS中征唬,源碼動畫最終調(diào)用的是UIView的animateWithDuration方法產(chǎn)生動畫效果。
該API適用時需添加
if (Platform.OS == 'android') {//android平臺需要開啟允許LayoutAnimation ios默認開啟
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
}
二茁彭、Animated
相比LayoutAnimation总寒,適用于更細微的變化過程動畫,可適配性更高理肺。如下簡單使用
export default class Test extends Component {
state = {
bounceValue: new Animated.Value(0),
};
render() {
return (
<Animated.Image source={require('./1.png')} style={{
transform: [{scale: this.state.bounceValue}]}}/>)
}
componentDidMount() {
Animated.timing(this.state.bounceValue,{
toValue:3,
duration:3000,
// useNativeDriver: true 這里先注釋掉摄闸,標記為注釋@1
}).start()
}
}
所有Animated的js源碼都在AnimatedImplementation.js中,本文RN版本為0.43妹萨,以下所有源碼都只提取了關鍵部分
先從動畫的入口函數(shù)Animated.timing說起年枕,該函數(shù)定義在第2060行
var timing = function (value: AnimatedValue | AnimatedValueXY,config: TimingAnimationConfig,): CompositeAnimation {
// 一些列轉化,最終執(zhí)行的是這個方法
singleValue.animate(new TimingAnimation(singleConfig), callback);
};
這里的singleValue是傳入的this.state.bounceValue乎完,為AnimatedValue類型熏兄。singleConfig就是我們調(diào)用Animated.timing方法傳入的第二個參數(shù)對象。接下來去了AnimatedValue這個類的animate方法
animate(animation: Animation, callback: ?EndCallback): void {
var handle = null;
var previousAnimation = this._animation;
this._animation && this._animation.stop();
this._animation = animation;
animation.start(
this._value,
(value) => {this._updateValue(value, true)},
(result) => {
callback && callback(result);
},
previousAnimation,this
);
}
這個方法也沒做啥實事囱怕,只是調(diào)用了傳入?yún)?shù)animation的start方法霍弹,這個參數(shù)就是上面?zhèn)鬟^來的new TimingAnimation(singleConfig)對象。這個對象的start方法如下
start(fromValue: number,onUpdate: (value: number) => void,onEnd: ?EndCallback,previousAnimation: ?Animation,animatedValue: AnimatedValue): void {
// 簡化后
this._onUpdate = onUpdate;
this._startTime = Date.now();
if (this._useNativeDriver) {
this.__startNativeAnimation(animatedValue);
} else {
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
}
}
這里出現(xiàn)了分支娃弓,當注釋@1打開時典格,_useNativeDriver屬性會為YES,動畫的執(zhí)行方式將交由原生端台丛,不再走下面的js端耍缴。本文只討論js端Animated動畫實現(xiàn)砾肺。
記錄下動畫開始的時間,然后調(diào)用requestAnimationFrame執(zhí)行onUpdate方法防嗡,requestAnimationFrame是跟原生端的定時器通信变汪,讓原生端定時器觸發(fā)回調(diào)事件onUpdate
onUpdate(): void {
var now = Date.now();
if (now >= this._startTime + this._duration) {. // 動畫時間結束調(diào)用
if (this._duration === 0) {
this._onUpdate(this._toValue);
} else {
this._onUpdate(
this._fromValue + this._easing(1) * (this._toValue - this._fromValue)
);
}
this.__debouncedOnEnd({finished: true});
return;
}
// 在這里更新傳入的this.state.bounceValue的值
this._onUpdate(this._fromValue +this._easing((now - this._startTime) / this._duration) * (this._toValue - this._fromValue));
if (this.__active) {
// 不斷注冊原生端定時器事件,循環(huán)
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
}
}
這個方法內(nèi)部會不斷注冊原生端定時器事件蚁趁,回調(diào)自身循環(huán)更新this.state.bounceValue的值裙盾,直到動畫時間結束退出。這里要注意他嫡,每一幀都會調(diào)用this._onUpdate方法番官。_onUpdate是start方法的第二個參數(shù),由上一個方法傳遞過來的钢属,最終執(zhí)行的是AnimatedValue的_updateValue方法
_updateValue(value: number, flush: bool): void {
this._value = value;
if (flush) {
_flush(this);
}
for (var key in this._listeners) { // AnimatedValue的值的變化過程是可監(jiān)控的徘熔,類似于KVO
this._listeners[key]({value: this.__getValue()});
}
}
方法傳遞到_flush,再到AnimatedProps的update方法,最終回調(diào)到AnimatedComponent的_attachProps方法中注冊的回調(diào)callback淆党。
var callback = () => {
if (this._component.setNativeProps) {
if (!this._propsAnimated.__isNative) {
this._component.setNativeProps(this._propsAnimated.__getAnimatedValue());
} else {
throw new Error('Attempting to run JS driven animation on animated '
+ 'node that has been moved to "native" earlier by starting an '
+ 'animation with `useNativeDriver: true`');
}
} else {
this.forceUpdate();
}
};
AnimatedComponent就是我們使用的Animated.Image酷师,this._component為Image。這里會判斷下我們使用的組件是否能夠設置setNativeProps染乌,只有那些能夠設置該屬性的組件山孔,如Image、Text慕匠、Image饱须、ScrollView才能使用Animated動畫,動畫的呈現(xiàn)原因是利用定時器不斷改變組件的setNativeProps值台谊,可避免觸發(fā)全局的render事件蓉媳,性能很高。