解析 iOS 動(dòng)畫原理與實(shí)現(xiàn)

這篇文章不會(huì)教大家如何實(shí)現(xiàn)一個(gè)具體的動(dòng)畫效果,我會(huì)從動(dòng)畫的本質(zhì)出發(fā),來說說 iOS 動(dòng)畫的原理與實(shí)現(xiàn)方式。

什么是動(dòng)畫

動(dòng)畫,顧名思義,就是能“動(dòng)”的畫。
人的眼睛對(duì)圖像有短暫的記憶效應(yīng),所以當(dāng)眼睛看到多張圖片連續(xù)快速的切換時(shí),就會(huì)被認(rèn)為是一段連續(xù)播放的動(dòng)畫了。

比如,中國古代的“走馬燈”,就是用的這個(gè)原理。
有些人還會(huì)在一個(gè)本子每頁上手繪一些漫畫,當(dāng)快速翻頁的時(shí)候,也會(huì)看到動(dòng)畫的效果,比如:


圖片來自網(wǎng)絡(luò)

計(jì)算機(jī)動(dòng)畫的實(shí)現(xiàn)方式

動(dòng)畫是由一張張圖片組成的,在計(jì)算機(jī)中,我們稱每一張圖片為 一幀畫面 。

如果我們想實(shí)現(xiàn)這么一個(gè)動(dòng)畫:一個(gè)水杯放在桌子的左邊,移動(dòng)到右邊,那么我們實(shí)際操作的,只是水杯。
所以動(dòng)畫的實(shí)現(xiàn),只是對(duì)運(yùn)動(dòng)變化了的部分的處理。

逐幀 與 關(guān)鍵幀

類似于上面提到的手繪翻頁方式,我們可以將這個(gè)水杯在每幀畫面中的位置一一找出來,這樣實(shí)現(xiàn)動(dòng)畫的方式就叫作 逐幀動(dòng)畫,我們需要處理動(dòng)畫中的每一幀。

我們一般在計(jì)算機(jī)上用 FPS ( Frames Per Second) ,即 每秒的幀數(shù) 來表示動(dòng)畫的刷新速度,基于屏幕的刷新率等其他原因,在計(jì)算機(jī)上一般采用 60 FPS。
如果運(yùn)動(dòng)變化幅度較緩,減半到 30 FPS 時(shí),我們?nèi)庋垡彩强山邮艿摹?br> 較低的 FPS 會(huì)讓我們有“卡頓”的感覺。

逐幀動(dòng)畫是最直接的,但要處理的幀數(shù)太多,所以實(shí)現(xiàn)過程是會(huì)麻煩。

計(jì)算機(jī)的工作就是來完成重復(fù)單調(diào)的工作的,所以,有些工作是可以考慮讓計(jì)算機(jī)來完成的。


上面的例子,可以變成一個(gè)涉及數(shù)學(xué)和物理的問題:一個(gè)杯子初始位置在左邊,n秒后勻速運(yùn)動(dòng)到右邊,那么在每 1/60 秒的時(shí)候,這個(gè)杯子的位置顯然是可以計(jì)算出來的了。
所以,我們其實(shí)只需要指定一些 關(guān)鍵 信息就能讓計(jì)算機(jī)自己計(jì)算出每一幀杯子的位置了:

  • 起始位置,比如一個(gè)坐標(biāo) (0,0)
  • 結(jié)束位置,再比如一個(gè)坐標(biāo) (100,0)
  • 動(dòng)畫總時(shí)間,比如 0.25 秒
  • 勻速運(yùn)動(dòng)

這種方式就稱之為 關(guān)鍵幀動(dòng)畫。即我們只需要給定幾個(gè)關(guān)鍵幀的畫面信息,關(guān)鍵幀與關(guān)鍵幀之間的過渡幀都將由計(jì)算機(jī)自動(dòng)生成。

這里說的 關(guān)鍵幀動(dòng)畫,是指的廣義上的一種動(dòng)畫制作方式,并不僅指 CAKeyframeAnimation,CABasicAnimation的實(shí)現(xiàn)方式也屬于 關(guān)鍵幀動(dòng)畫

iOS 動(dòng)畫

說完廣義上的動(dòng)畫,就可以來說說 iOS 的動(dòng)畫了。
先來說說動(dòng)畫的本質(zhì)。

動(dòng)畫的本質(zhì)

繼續(xù)用上面的簡(jiǎn)單例子:一個(gè) UIView 從 (0,0) 勻速移動(dòng)到 (100,0)的動(dòng)畫,動(dòng)畫總時(shí)間是0.25秒。
假設(shè)我們基于 60 FPS 來顯示動(dòng)畫,那么在0.25秒內(nèi)就應(yīng)該有15幀畫面,在每幀畫面中,這個(gè) UIViewx坐標(biāo),每次應(yīng)移動(dòng) 100/15 的距離。
如果我們每隔 0.25/15 秒刷新一次UIViewx坐標(biāo),那么就能實(shí)現(xiàn)這個(gè)動(dòng)畫效果了。
對(duì)于 x坐標(biāo)而言,每幀的位置就可以通過一個(gè)基于時(shí)間變化量的函數(shù)來求得:x=f(t) 。

所以,一個(gè)動(dòng)畫的本質(zhì),就是動(dòng)畫對(duì)象(這里是 UIView)的狀態(tài),基于時(shí)間變化的反應(yīng)了。
簡(jiǎn)單說,就是給定任意一個(gè)時(shí)刻,如果你都能得到這個(gè)動(dòng)畫對(duì)象的位置和、形狀等等屬性,你就能實(shí)現(xiàn)這個(gè)動(dòng)畫了。
屬性值的變化,既可能是位置、透明度、旋轉(zhuǎn)角度等的變化,也包括形狀的改變,比如從一條直線變化成一個(gè)圓圈,目標(biāo)就是要得到變化過程中特定時(shí)刻的中間態(tài)。

動(dòng)畫的實(shí)現(xiàn)

我們也可將 iOS 的動(dòng)畫分為兩大類:

  • 系統(tǒng)提供的 關(guān)鍵幀動(dòng)畫 實(shí)現(xiàn)方式;用戶指定 關(guān)鍵 信息,系統(tǒng)實(shí)現(xiàn)動(dòng)畫過程,對(duì)用戶而言操作起來會(huì)簡(jiǎn)單些。
  • 逐幀動(dòng)畫 實(shí)現(xiàn)方式;用戶自己 出每一幀畫面,系統(tǒng)操作方法簡(jiǎn)單,但用戶操作的工作量就會(huì)大一些。

逐幀動(dòng)畫實(shí)現(xiàn)方式

簡(jiǎn)單的說,要實(shí)現(xiàn)逐幀的方式,就是需要 周期性 的調(diào)用 繪制 方法,繪制每幀的動(dòng)畫對(duì)象。

這里說的 繪制,不光是指覆寫 UIView- drawRect:的方法來手動(dòng)重繪視圖,也包括修改 UIView 它的屬性,比如位置、顏色等。

iOS 的動(dòng)畫都是基于 CALayer 的,iOS 的 UIView 背后都有一個(gè)對(duì)應(yīng)的 CALayer 。對(duì) UIView 的修改實(shí)際上都是對(duì)背后 CALayer 的修改。
但如果在逐幀繪制的方法中修改了一個(gè)自建的 CALayer,這個(gè) CALayer 不是對(duì)應(yīng)某個(gè) UIView 的,需注意系統(tǒng)的 隱式動(dòng)畫 的影響,后面會(huì)提到這點(diǎn)。

周期性,就需要一個(gè)定時(shí)器來完成了,即 CADisplayLink。
CADisplayLinkNSTimer 比較類似,可以周期性的調(diào)用指定的方法。
之所以用 CADisplayLink,是因?yàn)樗腔谄聊凰⑿侣实?,即屏幕每次刷新時(shí)就會(huì)觸發(fā)調(diào)用。
iPhone 的屏幕刷新率是 60 FPS。

如果繪制過程過于復(fù)雜,不能在屏幕刷新一幀的時(shí)間內(nèi)完成,可以考慮改為每隔一幀繪制,相當(dāng)于是 30 FPS的刷新率。
不然可能會(huì)使動(dòng)畫不連貫,有卡頓感。

用逐幀方法繪制的原理不是很麻煩,麻煩的是繪制過程。
對(duì)于一個(gè)復(fù)雜動(dòng)畫,你可能需要運(yùn)用各種物理、幾何知識(shí)去計(jì)算視圖中間狀態(tài)的信息。
比如要實(shí)現(xiàn)一條直線卷曲變化為一個(gè)圓的動(dòng)畫,你就需要計(jì)算出中間態(tài)的曲線的彎曲程度和位置。

著名的 facebook 的 pop 動(dòng)畫框架,就是使用 CADisplayLink 這種逐幀繪制的方式實(shí)現(xiàn)的。

關(guān)鍵幀動(dòng)畫實(shí)現(xiàn)方式

采用關(guān)鍵幀的方式來實(shí)現(xiàn)動(dòng)畫,要講的內(nèi)容相對(duì)逐幀的方式就多的多了。

還是用 UIView 移動(dòng)的簡(jiǎn)單例子。
這里面有兩個(gè)關(guān)鍵幀,起始幀和結(jié)束幀,除此之外還有2個(gè)關(guān)鍵信息:

  • 起始幀,變化信息:坐標(biāo)為 (0,0)
  • 結(jié)束幀,變化信息:坐標(biāo)為 (100,0)
  • 動(dòng)畫時(shí)間,0.25秒
  • 勻速運(yùn)動(dòng)

坐標(biāo) 信息是 UIView 的一個(gè)屬性(實(shí)際是對(duì)應(yīng)到 CALayer 的屬性),在動(dòng)畫實(shí)現(xiàn)里,我們只需要指定起始和結(jié)束的兩個(gè)關(guān)鍵值就夠了,中間的過渡值都有系統(tǒng)自動(dòng)生成。
這里出現(xiàn)了兩種值,一個(gè)是我們?cè)O(shè)定的,一個(gè)是系統(tǒng)生成的,所以要先在這里插入一個(gè) 模型層展現(xiàn)層 的概念了

CALayer 的同一個(gè)屬性值,會(huì)分別保存在模型層 modelLayer ,和展現(xiàn)層 presentationLayer 中。當(dāng)我們修改屬性值時(shí),是修改的模型層的數(shù)值,動(dòng)畫時(shí)系統(tǒng)根據(jù)模型層的變化,生成的過渡值,是保存在展現(xiàn)層中的。

CALayer 的對(duì)象里能直接訪問到這兩層的信息。
CALayer 的底層實(shí)現(xiàn)實(shí)際不止這兩層,但我們現(xiàn)在討論動(dòng)畫的時(shí)候,可以只關(guān)心這兩層。

在整個(gè)動(dòng)畫過程中,呈現(xiàn)出來的過程是這樣的:

  1. 動(dòng)畫前,顯示模型層的當(dāng)前值;
  2. 動(dòng)畫開始,切換顯示展現(xiàn)層的值;
  3. 動(dòng)畫過程中,展現(xiàn)層的值根據(jù)時(shí)間變化,我們看到的實(shí)際是展現(xiàn)層的值在變化;
  4. 動(dòng)畫結(jié)束,切換回顯示模型層的值,此時(shí)模型層的值應(yīng)被修改為動(dòng)畫結(jié)束時(shí)的值。

用一段代碼來解釋下動(dòng)畫過程。

    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
    view.backgroundColor = [UIColor redColor];
    [self.view addSubview:view];

    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.fromValue = [NSValue valueWithCGPoint:CGPointMake(50, 0)];
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 0)];
    [view.layer addAnimation:animation forKey:nil];

//    view.frame = CGRectOffset(view.frame, 100, 0);

你會(huì)發(fā)現(xiàn)動(dòng)畫結(jié)束后,view 又跳回了原來的位置,這是因?yàn)樽詈笠恍写a注釋了,而這行代碼的功能就是實(shí)現(xiàn)第4步,將模型層的值修改為動(dòng)畫結(jié)束時(shí)的值。

動(dòng)畫實(shí)現(xiàn)

代碼中的 CABasicAnimation 就是真正的動(dòng)畫實(shí)現(xiàn)部分,也就是設(shè)定關(guān)鍵幀信息的地方。

將動(dòng)畫加入 CALayer 的代碼定義為:
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key
接受的類型是 CAAnimation 類型,有下面這些子類:

  • CABasicAnimation,可設(shè)定起始結(jié)束兩個(gè)關(guān)鍵幀的信息。
  • CAKeyframeAnimation,除首尾外,還可添加多個(gè)中間關(guān)鍵點(diǎn)。
  • CAAnimationGroup ,可組合多個(gè)動(dòng)畫,因?yàn)樯厦鎯煞N動(dòng)畫一次只能設(shè)置一個(gè)屬性值。
  • CATransition,圖層過渡動(dòng)畫,默認(rèn)是淡入。比如修改一個(gè) CALayer的背景色時(shí),是從初始色慢慢淡入過渡到結(jié)束色。
    可修改為新顏色把舊顏色頂出去等效果。還可使用 CIFilter 濾鏡做過渡效果,一些開源 UIViewController 的過渡動(dòng)畫使用了這種方式。

動(dòng)畫中,除了屬性值外,我們還設(shè)置了兩個(gè)和時(shí)間有關(guān)的信息:動(dòng)畫時(shí)間0.25秒,運(yùn)動(dòng)方式是勻速運(yùn)動(dòng)。

動(dòng)畫持續(xù)時(shí)間很簡(jiǎn)單,是通過 CAAnimation 遵守的 CAMediaTiming 協(xié)議設(shè)定的。

勻速運(yùn)動(dòng)是通過設(shè)置 CAAnimationtimingFunction 實(shí)現(xiàn)的,這是一個(gè) CAMediaTimingFunction 類的對(duì)象。

之前已經(jīng)說到,動(dòng)畫過程實(shí)際是一個(gè)時(shí)間的函數(shù),橫坐標(biāo)是時(shí)間的變化值,縱坐標(biāo)是動(dòng)畫屬性的變化量。那么我們就可以在一個(gè)直角坐標(biāo)系中,通過作圖來畫出這個(gè)函數(shù)。比如勻速運(yùn)動(dòng)的圖形,就是一條通過原點(diǎn)的直線。

所以這個(gè)類的功能就是畫出一條曲線,來表示時(shí)間和屬性變化之間的關(guān)系。而畫圖的方法,是使用的是畫貝葉斯曲線的方法。

系統(tǒng)提供了幾個(gè)常用的函數(shù),比如 kCAMediaTimingFunctionLinear 就是勻速運(yùn)動(dòng);kCAMediaTimingFunctionEaseInEaseOut 就是一般系統(tǒng)動(dòng)畫的默認(rèn)值,漸入漸出,即在動(dòng)畫開始和結(jié)束的時(shí)候速度稍慢些。

圖片來源自網(wǎng)絡(luò)

隱式動(dòng)畫

上面的過程,我們是 顯式 的向一個(gè) CALayer 添加了一個(gè)動(dòng)畫,所以這種方式叫做 顯式動(dòng)畫。
對(duì)應(yīng)的,還有 隱式動(dòng)畫,即系統(tǒng)自動(dòng)添加上的動(dòng)畫。

    CALayer *layer = [CALayer layer];
    layer.backgroundColor = [UIColor greenColor].CGColor;
    layer.frame = CGRectMake(0, 0, 100, 100);
    [self.view.layer addSublayer:layer];

    layer.frame = CGRectOffset(layer.frame, 100, 0);

這段代碼里,我們沒有添加 CAAnimation 動(dòng)畫,但 layer 不是直接變化到新的位置,而是有一個(gè)動(dòng)畫效果。
這就是 隱式動(dòng)畫 的效果。

當(dāng)我們改變 CALayer 的一個(gè)可動(dòng)畫的屬性值時(shí),就會(huì)觸發(fā)系統(tǒng)的隱式動(dòng)畫。
可動(dòng)畫的屬性值,可以在 CALayer 的文檔中找到,屬性說明中標(biāo)有 ** Animatable** 的,就是可自動(dòng)添加動(dòng)畫的屬性。

但是,有一個(gè)例外,對(duì)于 UIView 背后對(duì)應(yīng)的 CALayer,系統(tǒng)關(guān)閉了隱式動(dòng)畫,所以當(dāng)我們直接修改 UIView 或者是其底層的 CALayer 時(shí),變化是直接生效的,沒有動(dòng)畫效果。

所以當(dāng)我們?cè)谥饚绞缴蓜?dòng)畫時(shí),是可以直接修改 UIView 或者是其底層的 CALayer 的信息。
但是如果修改的是一個(gè)自建的單獨(dú) CALayer 時(shí),幀與幀之間的變化還是會(huì)觸發(fā)系統(tǒng)的默認(rèn)隱式動(dòng)畫,這個(gè)時(shí)候就需要我們來手動(dòng)關(guān)閉隱式動(dòng)畫。
當(dāng)快速動(dòng)畫的時(shí)候不會(huì)察覺到這點(diǎn),但這明顯會(huì)帶來性能上的浪費(fèi)。

隱式動(dòng)畫所做的事情和顯示動(dòng)畫是一樣的,我們?cè)O(shè)置的屬性值都是模型層的數(shù)值,而系統(tǒng)會(huì)自動(dòng)添加屬性對(duì)應(yīng)的 CAAnimation 動(dòng)畫到 CALayer 上。

UIView 有一系列的 animateWithDuration 動(dòng)畫方法,在這些方法中 UIView 會(huì)恢復(fù)隱式動(dòng)畫,所以在動(dòng)畫的 block 中修改屬性時(shí),又會(huì)觸發(fā)隱式動(dòng)畫。


那么系統(tǒng)是如果知道對(duì)一個(gè)屬性應(yīng)該添加哪種動(dòng)畫呢,這就需要讓 CAAction 協(xié)議登場(chǎng)了。

當(dāng)修改一個(gè) CALayer 的屬性時(shí),它會(huì)通過 - actionForKey: 來查詢這個(gè)屬性對(duì)應(yīng)的 action,而 key 就是對(duì)應(yīng)的屬性名稱。
CAAnimation 遵守 CAAction 協(xié)議,返回的 action 其實(shí)是個(gè) CAAnimation 動(dòng)畫。
也就是說, CALayer 通過 - actionForKey: 來查詢某個(gè)屬性被修改時(shí),需要調(diào)用哪個(gè)動(dòng)畫去展現(xiàn)這個(gè)變化。
一般默認(rèn)返回的是 CABasicAnimation ,默認(rèn)動(dòng)畫時(shí)間 0.25秒,時(shí)間函數(shù)為漸入漸出 kCAMediaTimingFunctionEaseInEaseOut。

- actionForKey: 查詢 action 的步驟有4步,在這個(gè)方法中有詳細(xì)的說明。
其中一種方式就是通過 CALayer 的 delegate 返回 action。而對(duì)于 UIView 背后對(duì)應(yīng)的 CALayer,其代理就是它對(duì)應(yīng)的 UIView,UIView 就是用這種方式關(guān)閉了隱式動(dòng)畫。

動(dòng)畫事務(wù)

創(chuàng)建動(dòng)畫事務(wù)的目的是為了操作的原子性,保證動(dòng)畫的所有修改能同時(shí)生效。
CATransaction 就是動(dòng)畫事務(wù)的操作類。

在創(chuàng)建隱式動(dòng)畫的時(shí)候,系統(tǒng)也會(huì)隱式的創(chuàng)建一個(gè)動(dòng)畫事務(wù),以保證所有的動(dòng)畫能同時(shí)進(jìn)行。

除此之外,還可以顯式的創(chuàng)建一個(gè)事務(wù)。
顯式事務(wù)中可以定義事務(wù)中所有動(dòng)畫的運(yùn)行時(shí)間和時(shí)間函數(shù),此外,還有這個(gè)方法 + (void)setDisableActions:(BOOL)flag 能顯式的關(guān)閉這個(gè)事務(wù)中的 action 查詢操作。
關(guān)閉了查詢也就是關(guān)閉了動(dòng)畫效果,屬性值的變化就會(huì)立即生效,而沒有動(dòng)畫效果了:

    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    ///...
    layer.frame = CGRectOffset(layer.frame, 100, 0);
    ///...
    [CATransaction commit];

注意別把 CATransaction 和 CATransition 搞混了,一個(gè)單詞是 transaction 事務(wù),另一個(gè)是 transition 轉(zhuǎn)變。

對(duì)比 總結(jié)

關(guān)鍵幀動(dòng)畫的實(shí)現(xiàn)方式,只需要修改某個(gè)屬性值就可以了,簡(jiǎn)單方便,但涉及的深層次內(nèi)容較多,需要更多的理解和練習(xí)。

采用逐幀動(dòng)畫的實(shí)現(xiàn)方式,實(shí)現(xiàn)原理簡(jiǎn)單,但繪制動(dòng)畫的過程要復(fù)雜。如果動(dòng)畫過程處理的事情較多,也會(huì)帶來較大的開銷,就有可能造成動(dòng)畫幀數(shù)的下降,出現(xiàn)卡頓的現(xiàn)象,因此需要較多的測(cè)試和調(diào)試。
動(dòng)畫繪制的過程中,會(huì)要求較多的數(shù)學(xué)、物理等知識(shí)來計(jì)算中間態(tài)的數(shù)據(jù)。

但這兩種方式也不是絕對(duì)分離開的。
關(guān)鍵幀動(dòng)畫實(shí)現(xiàn)方式,一般只能對(duì)系統(tǒng)實(shí)現(xiàn)了可動(dòng)畫的屬性做動(dòng)畫處理,但其實(shí)也是允許實(shí)現(xiàn)自定義屬性的動(dòng)畫處理的。
這就需要自己來實(shí)現(xiàn)系統(tǒng)中自動(dòng)計(jì)算過渡幀的操作了,也就是逐幀實(shí)現(xiàn)動(dòng)畫的方式了。
實(shí)現(xiàn)自定義屬性的動(dòng)畫可以參考這篇文章: Layer 中自定義屬性的動(dòng)畫

對(duì)于 iOS 系統(tǒng)提供的動(dòng)畫方法,上面只是從整體的角度作了一個(gè)全面的整理,還有很多細(xì)節(jié)內(nèi)容沒有寫出來,比如 CALayer 的三維變換、CAKeyframeAnimation 的延路徑動(dòng)畫,CAMediaTiming 的時(shí)間控制,等等。感興趣的話,可以再看看這些內(nèi)容:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,698評(píng)論 6 30
  • 在iOS中隨處都可以看到絢麗的動(dòng)畫效果,實(shí)現(xiàn)這些動(dòng)畫的過程并不復(fù)雜,今天將帶大家一窺iOS動(dòng)畫全貌。在這里你可以看...
    F麥子閱讀 5,273評(píng)論 5 13
  • 什么是動(dòng)畫 動(dòng)畫,顧名思義,就是能“動(dòng)”的畫。 人的眼睛對(duì)圖像有短暫的記憶效應(yīng),所以當(dāng)眼睛看到多張圖片連續(xù)快速的切...
    CoderSC閱讀 1,354評(píng)論 0 1
  • 最近在做iOS界面轉(zhuǎn)場(chǎng)的動(dòng)畫,寫完轉(zhuǎn)場(chǎng)入口后基本元素還是回歸到我們常用的基本動(dòng)畫代碼,有關(guān)動(dòng)畫的帖子網(wǎng)絡(luò)上一搜一大...
    大雄記閱讀 5,653評(píng)論 0 18
  • 什么是動(dòng)畫動(dòng)畫,顧名思義,就是能“動(dòng)”的畫。人的眼睛對(duì)圖像有短暫的記憶效應(yīng),所以當(dāng)眼睛看到多張圖片連續(xù)快速的切換時(shí)...
    M_慕宸閱讀 3,645評(píng)論 0 6

友情鏈接更多精彩內(nèi)容