深度剖析UIScrollView與阻尼動畫

摘要

UIScrollView是iOS開發(fā)中不可或缺也是使用最多的基礎(chǔ)組件孽椰;常用的Feed流岖赋、Pager芒涡、輪播圖等等都與UIScrollView存在著密不可分的關(guān)系浮驳。日常開發(fā)中,我們通常都局限于其必要的幾個調(diào)用接口和代理滑负,而不曾探究過其隱藏在那幾個簡單接口背后那些精妙的設(shè)計在张,比如:滾動視圖如何在有限的區(qū)域內(nèi)展示無限的內(nèi)容?每一次在滾動區(qū)域觸控屏幕會產(chǎn)生哪些反應(yīng)橙困?它在現(xiàn)實(shí)世界中又是怎樣的物理形態(tài)瞧掺?本文從基本的參數(shù)觀測開始,以數(shù)學(xué)凡傅、物理學(xué)和優(yōu)化方法中的一些基本方法和概念為工具辟狈,探索UIScrollView流暢交互背后隱藏的規(guī)律,領(lǐng)略蘋果工程師在實(shí)現(xiàn)一個基礎(chǔ)組件時所做出的精妙設(shè)計夏跷。


UIScrollView的局部顯示原理

為了印證這部分觀點(diǎn)哼转,我們從蘋果的官方文檔上摘抄了一段描述:

*Documents:UIScrollView is the superclass of several UIKit classes, including UITableView and UITextView. *

*A scroll view is a view with an origin that’s adjustable over the content view. A scroll view tracks the movements of fingers, and adjusts the origin accordingly[1]. The view that shows its content through the scroll view draws that portion of itself according to the new origin, which is pinned to an offset[2] in the content view. By default, it bounces[3] back when scrolling exceeds the bounds of the content. *

這段文字的大意有以下幾點(diǎn):

  • UIScrollView會追蹤手指的動作并適當(dāng)調(diào)整顯示內(nèi)容的原點(diǎn)(一個矩形左上角所在的坐標(biāo))。

  • 根據(jù)被調(diào)整矩形的原點(diǎn)坐標(biāo)展示不同的內(nèi)容槽华。

  • 默認(rèn)在接觸到邊界的時候會反彈壹蔓。

  • 同時,UIScrollView是UITableView猫态、UITextView的父類(也包括UICollectionView)佣蓉。

從UIScrollView的父類UIView的角度出發(fā),UIView的屬性:bounds.origin(x,y) 標(biāo)記了一個UIView的所有子元素依賴的參考系原點(diǎn)亲雪,被添加在這個UIView上的所有子視圖在繪制時均會參考這個原點(diǎn)勇凭,這意味著:如果這個原點(diǎn)被標(biāo)記為{-40, -40.f},那么這個視圖的所有子視圖都會基于(-40, -40)這個點(diǎn)繪制义辕。例如虾标,這種情況下,一個frame = {20.f,20.f,100.f,100.f} 的子視圖 會從點(diǎn)(-20.f, -20.f) 開始繪制,-20的來源是子視圖的原點(diǎn)(20,20)加偏移量(-40,-40)灌砖,所以璧函,在此種情況下你只能看到這個子元素右下角大小為20*20的一小部分,其余超出邊界的部分無法看到傀蚌。

在UIScrollView中,為了將這個特性與常規(guī)的UIView區(qū)分開來蘸吓,bounds.origin 被獨(dú)立出來叫做:contentOffset,兩者都包含兩個數(shù)值x善炫、y的二維向量(CGPoint),只要根據(jù)用戶手勢美澳、動畫規(guī)律不斷變更c(diǎn)ontentOffset销部,就能做出滾動的效果。

調(diào)整contentOffset并非難事制跟,但想做出順暢的滾動效果卻需要考慮很多方面;這體現(xiàn)了蘋果提供基礎(chǔ)組件的精妙之處酱虎,讓我們可以不了解任何細(xì)節(jié)的同時對UIScrollView操作自如雨膨,而將大部分復(fù)雜邏輯封裝在了輕描淡寫的一句:accordingly背后。

UIScrollView交互細(xì)節(jié)

我們匯總了在setContentOffset的之前需要考慮的所有情況:

  1. 當(dāng)panGesture在容器的規(guī)定范圍(contentSize)內(nèi)生效時读串,UIScrollView通常要移動相同的位置和方向聊记,contentOffset與panGesture位移距離保持1:1數(shù)值關(guān)系。

  2. 在panGesture結(jié)束后恢暖,如果Gesture有剩余速度依然生效排监,那么在之后的一段時間里要持續(xù)進(jìn)行減速。

  3. 在panGesture結(jié)束后杰捂,如果當(dāng)前的contentOffset超出了contentSize舆床,在之后的一段時間里需要進(jìn)行反彈并恢復(fù)到邊界處。

  4. (2)和(3)結(jié)合起來嫁佳,panGesture結(jié)束后挨队,有速度依然生效,在之后的一段時間內(nèi)減速蒿往,減速未結(jié)束的情況下觸及了邊界盛垦,開始進(jìn)行反彈,并回彈到非拉伸狀態(tài)瓤漏。

  5. 根據(jù)正交向量互不影響腾夯,contentOffset需要在x、y兩個方向上獨(dú)立生效蔬充。

  6. 在超過邊界進(jìn)行panGestures時蝶俱,panGesture轉(zhuǎn)化為contentOffset距離的有效比例逐漸減小,呈現(xiàn)出一種拉扯到極限的效果娃惯。

(如果我們只在panGesture生效期間setContentOffset跷乐,這樣做出來的效果和在屏幕上畫一個可以跟手的View別無二致,這種感覺就像你在通常的Windows桌面拖拽一個文件夾到處移動一樣)趾浅。

正是這些復(fù)雜的交互特性才使得UIScrollView在我們的手中呈現(xiàn)出了絕佳的交互體驗(yàn)愕提。

Decelerate運(yùn)動探究

數(shù)值觀測

由簡入繁馒稍,首先從比較容易的Decelerate動畫開始,觀察這部分動畫的運(yùn)行規(guī)律浅侨,我們不妨創(chuàng)建一個常規(guī)的UIScrollView(為了清楚地觀測滾動纽谒,最好創(chuàng)建UICollectionView來用格子劃分整個contentSize),在以下代理中打印出統(tǒng)計信息 :


- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset

{

    NSLog(@"DecelerateVelocity:%lf", velocity.y);

    NSLog(@"DecelerateDistance:%lf", targetContentOffset->y - scrollView.contentOffset.y);

}

  • DecelerateVelocity記錄了如输,panGesture手勢結(jié)束瞬間的瞬時速度鼓黔,也就是Decelerate起始速度。

  • DecelerateDistance記錄了不见,panGesture結(jié)束后直到停止澳化,在這段時間內(nèi),自然減速移動的總距離

我們截取了幾次panGesture手勢結(jié)束瞬間捕獲到的數(shù)據(jù)稳吮,為了保證不受到bounces和邊界的影響缎谷,應(yīng)當(dāng)盡量保證這些減速過程中不會觸及邊界,數(shù)據(jù)如下:

序號 減速初速度 截止到停止的移動距離
1 5.0270956 2506.5
2 1.802126 895.0
3 1.412374 700.5
4 1.687861 838.0
  • 不難看出二者之間的關(guān)系:不考慮1000倍的話灶似,減速的初速度總是它依靠慣性移動總距離的2倍列林。

關(guān)于這個1000倍:我們從這個代理的注釋中可以看出:

Called on finger up if the user dragged. velocity is in points/millisecond. targetContentOffset may be changed to adjust where the scroll view comes to rest

那么這個速度的單位是points/millisecond,而通常的速度應(yīng)以points/second為單位酪惭;因此希痴,我們默認(rèn)為這個速度乘1000以方便之后的計算,同時我們將1point的寬/高視為單位長度1米春感,這樣速度單位也被我們統(tǒng)一為標(biāo)準(zhǔn)速度單位:m/s砌创。

結(jié)果分析

根據(jù)上面的觀察結(jié)果,我們試圖尋找一種常用的函數(shù)甥厦,這個函數(shù)的特點(diǎn)是:他對時間(t)的導(dǎo)數(shù)(v)似乎總他自身(y)的兩倍纺铭。

那么顯然這個函數(shù)和其導(dǎo)數(shù)分別是:

y=C_1+C_2e^{2t},v=C_2e^{2t}

這兩個函數(shù)是指數(shù)加速,而由于Decelerate是減速刀疙,所以我們需要根據(jù)實(shí)際情況增加一個負(fù)號來保證速度總是隨著時間逐漸減小的舶赔,所以有:

y=C_1-C_2e^{-2t},v=-2C_2e^{-2t}

(細(xì)心的同學(xué)可以發(fā)現(xiàn)除了指數(shù)增加了負(fù)號外,C2常數(shù)前面也加了一個負(fù)號谦秧,這是因?yàn)槲覀冊谏鲜鲇^測數(shù)值的時候velocity直接使用了初速度v0竟纳,而位移卻是用了末位移減初位移;而正確的做法是使用速度改變量疚鲤,也就是(0-v0),因?yàn)槟顟B(tài)是停止?fàn)顟B(tài)锥累;因此真正的結(jié)論應(yīng)該是:速度的改變量應(yīng)當(dāng)總是位移改變量的-2倍,但此前為了數(shù)值觀測方便集歇,沒有強(qiáng)調(diào)正負(fù)關(guān)系桶略。)

那么根據(jù)已知條件:當(dāng)減速剛開始t=0時,v=v0,因此际歼,右側(cè)的常系數(shù)C2可以被固定為v0惶翻;而C1則是一個跟初始位置相關(guān)的常數(shù),此處我們不關(guān)心鹅心。

那么最終我們得到的速度衰減公式為:

v=v_0e^{-2t}


相關(guān)解釋

以下是正常的推導(dǎo)(來解釋那個突兀的指數(shù)函數(shù)):

以不同的速度v1吕粗、v2開始的減速效果滿足相同的運(yùn)動規(guī)律,不妨令v2>v1旭愧,那么v1是v2減為0的必經(jīng)狀態(tài)颅筋,那么根據(jù)之前的觀察結(jié)果:

(0-v_ *{1}) = -2y_1,(0-v_2)=-2y_2

兩式相減:

(v_1-v_2)=-2(y_1-y_2)

當(dāng)v1與v2無限接近時:

dv = -2dy

兩邊同時除以時間間隔dy:

*\frac{dv}{dt} = -2\frac{dy}{dt} ==> \frac{dv}{dt}=-2v ==> \frac{1}{v} dv=-2dt*

兩邊積分:

\ln{v}=-2t+C ==> v = e^{-2t+C} ==> v=e^Ce^{-2t}

顯然e^C = v_0;


結(jié)論

UIScrollView的減速運(yùn)動就是阻尼系數(shù)為2的阻尼運(yùn)動输枯,這種阻尼運(yùn)動對應(yīng)于現(xiàn)實(shí)中的低速流體阻力(不考慮壓縮損耗)议泵。

其他

  • Q:y與v的倍數(shù)關(guān)系為什么是約等于?

  • A:這個不是誤差用押,是Apple有意為之肢簿,因?yàn)樽枘釡p速是指數(shù)衰減,所以無限趨近于0的過程是非常漫長的蜻拨,如果你看v/t的關(guān)系會發(fā)現(xiàn),即使是t無限大桩引,v都不會減為0缎讼,因?yàn)橹笖?shù)總是正的,所以為省略后面那段很長很慢的減速坑匠,蘋果就把它截斷了血崭。 這個截斷量大概是 v = (12 ~ 13)pt/s , 小于這個速度就會被截斷,所以y總是比v/2小一點(diǎn)(6左右)厘灼。所以夹纫,這其實(shí)是嚴(yán)格遵循自然規(guī)律原則為提升用戶體驗(yàn)?zāi)繕?biāo)做出的讓步。

Bounces運(yùn)動探索

數(shù)值觀測

我們?nèi)绶ㄅ谥艱ecelerate觀測過程设凹,主要從時間空間兩個維度觀察Bounces運(yùn)動規(guī)律舰讹,那么觀測數(shù)值包括:

  • UIScrollView首次接觸到邊界時的初速度v0,一個直觀的感受就是當(dāng)這個v0越大時闪朱,反彈過程中能達(dá)到的最遠(yuǎn)距離就越長月匣,可以理解為慣性越大。

  • 那么第二個值得觀察的數(shù)值就是每次Bounces運(yùn)動所能達(dá)到超過邊界的最遠(yuǎn)距離奋姿。

  • 第三個數(shù)值為整個Bounces運(yùn)動過程總持續(xù)時間锄开,這個事件從首次接觸邊界開始,直到自然恢復(fù)到邊界處結(jié)束称诗。

具體的統(tǒng)計方法在此只做簡要介紹萍悴,讀者可自行編寫Demo進(jìn)行實(shí)踐:

  • 為了方便區(qū)分,我們每次令UIScrollView沖擊頂部的邊界,這樣當(dāng)contentOffset.y為負(fù)數(shù)時癣诱,意味著Bounces動畫開始计维,在此處記錄一個起始時間t0。

  • 為了獲取到精準(zhǔn)的初速度狡刘,我們每次在UIScrollView接觸到邊界前停止拖拽享潜,令其依賴自身慣性進(jìn)行反彈,這樣我們就可以依賴上文Decelerate中提供的規(guī)律計算出接觸邊界時的初速度:(停止拖拽時的速度-停止拖拽時距離頂部的距離*2)嗅蔬。

  • 最遠(yuǎn)距離剑按,通過在scrollViewDidScroll代理中不換捕獲最小值獲取。

  • 結(jié)束時間澜术,通過endDecelerate代理捕獲(Bounces結(jié)束時也被視為減速結(jié)束)艺蝴。

經(jīng)過這些記錄后,我們成功捕獲了幾組數(shù)據(jù):

序號 接觸邊界時初速度(p/s) Bounces最遠(yuǎn)距離(p) Bounces持續(xù)時間(s)
1 986.497 -32.5 0.6668
2 2404.116 -80.5 0.7509
3 1793.594 -60 0.7337
4 1251.628 -41.5 0.6836

觀察到的現(xiàn)象:

  • v0與最遠(yuǎn)距離y總是正比例關(guān)系鸟废,這個比例大概是30猜敢。
  • Bounces運(yùn)動總時長不會隨著慣性的變化而發(fā)生太大改變,這個值基本穩(wěn)定在0.6s~0.8s之間盒延。這是典型的阻尼運(yùn)動特點(diǎn)缩擂,類似于log(n)時間復(fù)雜度的算法不會n產(chǎn)生較大增量。

那么顯然Bounces中存才的兩種力:彈力和阻力添寺;彈力用來保證接觸到邊界發(fā)生反彈胯盯,阻力用來限制彈力的簡諧振動。

結(jié)果分析

我們列出經(jīng)過以上兩種合力的數(shù)學(xué)模型:

-ky - v*c = ma

  • 首項(xiàng)中k為胡克定律彈力模型中的勁度系數(shù)k计露,y為彈簧被壓縮的長度博脑,也就是UIScrollView超出邊界的距離。因?yàn)檫@個彈力總是與y相反票罐,所以符號是負(fù)號叉趣。
  • 次項(xiàng)中c為阻力的阻尼系數(shù)c(參考Decelerate中的數(shù)值2),阻力與速度v的方向總相反该押,所以符號為負(fù)號疗杉。
  • 整個式子就是我們熟知牛頓第二定律:合力等于質(zhì)量乘加速度。

另外一個我們熟知的結(jié)論是:瞬時速度v是位移y對時間t的導(dǎo)數(shù)沈善;瞬時加速度a是速度v對時間t的導(dǎo)數(shù)乡数,也就是位移y對時間t的二階導(dǎo)數(shù)。那么上述方程可寫成如下形式:

my''+cy'+ky=0

等式兩側(cè)同除質(zhì)量m:

y''+\frac{c}{m}y'+\frac{k}{m}y=0

這是個二階線性齊次微分方程闻牡,根據(jù)其特征根返程解個數(shù)有三種不同形式通解净赴,為了方便討論特征根,我們對其系數(shù)做一些代換罩润,令:

δ=\frac{c}{2m},ω=\sqrt{\frac{k}{m}}

注意玖翅,由于等式中已經(jīng)考慮了符號,所以阻尼系數(shù)c和進(jìn)度系數(shù)k在此處均為正數(shù);關(guān)于質(zhì)量m, 我們不考慮質(zhì)量小于等于0的情況金度;因此应媚,這些參數(shù)均為正數(shù),代換后也為正數(shù)猜极。

那么這個式子化簡為:

y''+2δy'+ω^2y=0

其特征根方程為:

λ^2+2δλ+ω^2=0

討論其解形式

情況1:4δ^2-4ω^2>0

特征根方程兩相異實(shí)根a,b中姜,其通解形式為:

y = C_1e^{at}+C_2e^{bt}

情況2:4δ^2-4ω^2=0

特征根方程一對相同實(shí)根,同為a跟伏,其通解形式為:

y=(C_1+C_2t)e^{at}

情況3:4δ^2-4ω^2<0

特征根方程實(shí)數(shù)域無根丢胚,復(fù)數(shù)域一對共軛復(fù)根:a-bia+bi受扳,其通解形式為:

y = e^{at}(C_1sin(bt)+C_2cos(bt))

這三種分別對應(yīng)阻尼震動的三種形式:過阻尼携龟、臨界阻尼和欠阻尼

從用戶體驗(yàn)角度講,在不發(fā)生震蕩的前提下反彈后以最快速度恢復(fù)到邊界通常會獲得最佳手感勘高。因此在這里峡蟋,蘋果必然會選擇臨界阻尼為自己通用組件邊界減震,因此我們選擇臨界阻尼條件4δ^2-4ω^2=0下的通解形式:

y = (C_1+C_2t)e^{at}

此時δ=ω华望,我們?nèi)ˇ娜锘龋敲唇馓卣鞲匠蹋?img class="math-inline" src="https://math.jianshu.com/math?formula=%CE%BB%5E2%2B2%CE%B4%CE%BB%2B%CE%B4%5E2%3D0" alt="λ^2+2δλ+δ^2=0" mathimg="1"> ,得到:λ=-δ=-ω

將λ解帶入替換通解的a赖舟,得到:

y = (C_1+C_2t)e^{-δt}

根據(jù)初始條件:Bounces開始時t=0,y=0匿又,得到C1=0,因此化簡為:

y = Cte^{-δt}

C建蹄、δ是兩個待定系數(shù),后面介紹測量方法裕偿,這里直接給出:C是首次接觸邊界時的速度v0洞慎,δ是常數(shù)10.9,故Bounces公式為:

y = v_0te^{-10.9t}


相關(guān)解釋

特征根方程與微分方程的關(guān)系:

對于二階齊次方程:y''+py'+yp=0 嘿棘,考慮一個函數(shù)y最容易湊出二階導(dǎo)劲腿、一階導(dǎo)、自身最容易提取出包含變量的公因式鸟妙,并能夠?qū)⑹S喑?shù)通過加減法抵消焦人。

那么這個函數(shù)理應(yīng)是指數(shù)函數(shù),因?yàn)橹笖?shù)函數(shù)的導(dǎo)數(shù)總是能提取出自身:

y=e^{λx},y'=λe^{λx},y''=λ^2e^{λx}...

如果確認(rèn)了這種解形式重父,那么此方程可寫為:

λ^2e^{λx}+pλe^{λx}+qe^{λx}=0 ==> (λ^2+pλ+q)e^{λx}=0

指數(shù)不可能為0花椭,所以左側(cè)多項(xiàng)式λ^2+pλ+q=0的解λ即是微分方程的解,所以這個簡化后的方程就是特征根方程房午。


結(jié)論

Bounces運(yùn)動是一種阻尼震動矿辽,這種震動依賴的彈性k和阻尼c滿足了一種特殊的數(shù)量關(guān)系,使得Bounces遵循臨界阻尼震動,從而呈現(xiàn)出一種“回彈永遠(yuǎn)不會彈過邊界或發(fā)生反復(fù)震動”現(xiàn)象袋倔。經(jīng)過以上討論雕蔽,我們嘗試構(gòu)建了一下UIScrollView的內(nèi)容展示區(qū)的復(fù)原圖如下:

  • UIScrollView的contentSize內(nèi)部充滿了阻力比較小的流體c=2,可理解為水宾娜,在圖中表現(xiàn)為藍(lán)色部分批狐。

  • contentSize外部充滿了阻力比較大的流體c=21.8,可理解為油前塔,在圖中表現(xiàn)為黃色部分嚣艇。

  • UIScrollView的contentSize邊界處安裝了4根彈簧,每根的進(jìn)度系數(shù)為119嘱根,在圖中表現(xiàn)為紫色部分髓废。

  • UIScrollView展示的內(nèi)容區(qū)域?yàn)橐粔K質(zhì)量為1kg的光滑鐵板,可以在整個區(qū)域內(nèi)跟隨手勢自由移動该抒,處于不同區(qū)域時收到相應(yīng)力的作用慌洪。

參數(shù)測量方法

由于存在C、δ兩個待定系數(shù)凑保,而我們能觀測到的數(shù)值主要是contentOffset冈爹,也就是位移,C欧引、δ 的影響因素是速度频伤、加速度這種更高級別的參數(shù),通過觀察位移確定A芝此、δ難度較高憋肖,所以在這里我們使用一種簡單的優(yōu)化方法來讓這我們預(yù)測的運(yùn)動趨勢與實(shí)際趨勢逐漸吻合。

梯度下降思路

  1. 給定一組UIScrollView沖擊邊界觸發(fā)Bounces的真實(shí)數(shù)據(jù)集合S婚苹,S內(nèi)包含Bounces動畫期間內(nèi)所有的contentOffset取值:[y0岸更,y1, y2, y3...]。

  2. 給定一組待定系數(shù)的解(C膊升、δ怎炊、φ),通過公式y=C(t+φ)e^{-δ(t+φ)} 計算出所有的理論值[y0'廓译,y1', y2', y3'...]评肆。

  3. 計算出理論值與實(shí)際值的方差:Sum=\sum_ *{n=0}^N(y_n-y'* _n)^2 ,Sum函數(shù)越小非区,理論曲線與實(shí)際曲線的越接近瓜挽。

  4. 如果我讓A、δ院仿、φ中任意一個值秸抚,單獨(dú)進(jìn)行變化速和,能夠讓目標(biāo)函數(shù)的值變小,那么就對這個變化予以肯定剥汤,將原值修改為變化后的值颠放。對于三個待定系數(shù),我們有六個方向進(jìn)行變化吭敢,A+△A碰凶、A-△A、δ+△δ鹿驼、δ-△δ欲低、φ+△φ、φ-△φ畜晰,當(dāng)這六中變化中有多種變化都可以讓目標(biāo)函數(shù)結(jié)果變小砾莱,那么我們?nèi)∽兊米钚〉哪莻€變化,因此這個方法也叫最速下降法凄鼻。

  5. 當(dāng)目標(biāo)函數(shù)被優(yōu)化到0時腊瑟,說明我們的目標(biāo)曲線與實(shí)際觀測出的曲線已經(jīng)完全重合了;而我們機(jī)器上觀測到的數(shù)值總是離散的块蚌,所以實(shí)際上闰非,這個數(shù)值被優(yōu)化到個位數(shù)時就已經(jīng)非常接近了。

以下是一組Bounces數(shù)據(jù)的優(yōu)化過程

  • φ的值是我們?yōu)榱俗屨w盡量收斂引入的偏移量峭范,我們給出的公式:y=Cte^{-δt}的前提是:總是認(rèn)為這個運(yùn)動開始時t = 0财松,但我們實(shí)際取到Bounces開始時候的y的值也許并不恰好是0,因?yàn)槭謾C(jī)中的屏幕刷新的信號都是離散的纱控,每0.0167s一次刷新辆毡,所以大部分情況下是從一個正數(shù)如:y=10直接變化到了一個負(fù)數(shù):y=-10,而不會恰好落在0處甜害,而我們?nèi)〉降牡谝粋€值就成了-10,這樣的話曲線整體在時間上會有一個微小的偏移胚迫, 所以加入了這個變量φ,可以讓結(jié)果收斂得更準(zhǔn)確唾那。

  • 一般didScroll的回調(diào)頻率是小于CADisplayLink回調(diào)頻率的,在滾動緩慢的狀態(tài)下褪尝,離散取整可能導(dǎo)致contentOffset在某次刷新中不發(fā)生變化闹获,也就是說didScroll的兩次打點(diǎn)間隔有一定可能大于0.0167s,是2個或者3個刷新周期河哑,因此使用CADisplayLink打點(diǎn)是最穩(wěn)妥的方法避诽,但后來經(jīng)過實(shí)踐這方面影響不大,因?yàn)榈退贍顟B(tài)下本身y值的差別就不大璃谨,對sum函數(shù)影響的比重非常小沙庐,所以使用didScroll打點(diǎn)鲤妥,默認(rèn)間隔是0.0167s即可。

最終我們對多組類似上面的數(shù)據(jù)進(jìn)行測量拱雏,δ值總是接近于10.9的一個數(shù)棉安;而C則根據(jù)每次Bounces的力度不同發(fā)生變化。

參數(shù)C的確定

由于我們發(fā)現(xiàn)了的大小受到每次Bounces的力度影響铸抑,因此找到了一個包含兩種參數(shù)的關(guān)系探究具體的算法贡耽,利用這個公式:

Ft=m(v-v_0)

F是合力,t是持續(xù)時間鹊汛,由于合力F中的彈力蒲赂、阻力兩部分隨時間變化,因此這兩部都被寫成積分形式(小c是阻尼系數(shù)刁憋,大C是待固定的常數(shù))

-\int_ *{0}^{t}kydt-\int*_{0}^{t}cv = m(0-v_0)

然后把上面那里拿到的y滥嘴、v的公式:

y=Cte^{-δt},v=Ce^{-δt}-δCte^{-δt}

代入到左邊那個積分里:

-\int_ {0}^{t}kCte^{δt}dt-\int_{0}^{t}c(Ce^{-δt}-δCte^{-δt})dt=-mv_0

發(fā)現(xiàn)左邊的積分里面總是能提取出來一個C,里面就沒有C了,只剩一堆已知的量,這個時候恰好右面有個v_0倾芝,所以C和初速度v_0是呈正比的范舀,比例系數(shù)就是m除以剩余的那一堆積分。不用算這個積分灸姊,我們只需要找到一組Bounces的數(shù)據(jù),通過減速的規(guī)律得到v_0,通過上面那個優(yōu)化方法優(yōu)化出此次Bounces與實(shí)際值最接近的C饺汹,(上面動圖里的第二個變量:a),求出此次Bounces中Cv_0的比例就是所有情況下的固定比值痰催,非常湊巧這個比值就是1兜辞。所以整體的算式就成為:

y = v_0te^{-10.9t}

其他相關(guān)數(shù)值:

勁度比:\frac{k}{m}=119

阻尼比:\frac{c}{m}=21.8

Bounces最遠(yuǎn)距離

根據(jù)y=v_0te^{-10.9t},滿足y'=0的點(diǎn)即是最遠(yuǎn)處夸溶,得到:

Ce^{-δt}-δCte^{-δt}=0

得到:

t=\frac{1}{δ}逸吵,y=\frac{v_0}{δe}

兩個結(jié)論:

  • Bounces總是會在開始后\frac{1}{10.9}秒時達(dá)到最遠(yuǎn)距離,到達(dá)最遠(yuǎn)距離的時間是固定的缝裁。

  • 最遠(yuǎn)距離確實(shí)與初速度呈正比扫皱,比例系數(shù)為\frac{1}{δe},這兩個:2.71828×10.9,近似于30捷绑,也就是我們此前在表格中觀察到的30韩脑。

Bounces遞推形式

考慮到我們自己實(shí)現(xiàn)一個CustomScrollView,在使用CADisplayLink執(zhí)行Bounces動畫時,已知的條件只有某個瞬間的瞬時速度v和當(dāng)前所在位置y粹污;而這個y不一定0段多,v也不一定是v0;所以在這里我們提供任意瞬時狀態(tài){v,y}轉(zhuǎn)v0的方法:

y = v_0te^{-δt},v=v_0e^{-δt}-δv_*0te^{-δt}*

兩式相除:

\frac{y'}{y}=\frac{1-10.9t}{t}=\frac{v}{y}

右側(cè)等式化簡的到t表達(dá)式:

t=\frac{1}{δ+\frac{v}{y}}

帶入y得到v_0表達(dá)式:

v_0=\frac{ye^{δt}}{t}

這樣壮吩,我們可以根據(jù)任意時刻的狀態(tài){v,y}計算出完整的Bounces表達(dá)式和當(dāng)前的時間t进苍,然后t=t+△t(△t=0.0167),使用y和y'的算式計算出下次刷新時候的{y加缘、v},不斷更新這個狀態(tài)觉啊,就實(shí)現(xiàn)了UIScrollView一樣Bounces變化拣宏。

實(shí)際開發(fā)中的用途

使用Decelerate在兩個UIScrollView減傳遞能量

你可以在下面這種多層UIScrollView嵌套的復(fù)雜結(jié)構(gòu)中使用Decelerate傳遞能量,這個結(jié)構(gòu)的最外層是縱向的滾動視圖柄延,承載了頭部蚀浆、可吸頂區(qū)域,和分頁容器三個部分搜吧;分頁容器是一個橫向的滾動視圖市俊;內(nèi)部又分為多個縱向滾動視圖,由此組成了三層嵌套UIScrollView滤奈,如下:

用戶在頭部和吸頂區(qū)域向上拖拽后生效的是最外層藍(lán)色的縱向視圖摆昧,當(dāng)藍(lán)色的層級觸底后,會有一個明顯的停頓效果蜒程;因?yàn)楦敢晥D的手勢生效绅你,剩余的慣性無法傳遞到內(nèi)部的橘黃色縱向視圖;為了彌補(bǔ)這部分缺陷昭躺,讓整個縱向列表看起來更加融為一體忌锯,我們?yōu)橥鈱拥乃{(lán)色視圖分配了一個剩余速度(或沖量)檢測的機(jī)制:

  • 這個Detector利用上文提及的 restVelocity = (velocity-2**distance)得到了觸及邊界后剩余的速度。*

  • 乘自身質(zhì)量m后通過圖中的藍(lán)色通路傳遞給當(dāng)前選中tab對應(yīng)的橘黃色視圖领炫。

  • 橘黃色視圖配置了一個沖量接收機(jī)制Impulser偶垮,將接收到的mv除去自身質(zhì)量,得到作用在自身的速度帝洪。

  • 橘黃色視圖以此速度為初速度似舵,執(zhí)行向下滾動的減速動畫。

Q:關(guān)于為什么傳遞mv

A:我們?yōu)槊總€Detector和Impulser分配了額外的一個屬性m葱峡,從而讓這這些動效可以在二者之間以不同比例傳遞砚哗,這看起來就像:一個密度較大的球體撞向了一個密度較小的球體。通常狀況下默認(rèn)質(zhì)量都是1砰奕,因此不同的UIScrollView之間的剩余速度會以1:1的比例傳遞蛛芥。

使用Bounces實(shí)現(xiàn)POP類型彈幕

你可以使用Bounces動畫制作一款POP類型彈幕軌道,我們使用與Bounces類似的參數(shù)關(guān)系構(gòu)建臨界阻尼军援,使用此種曲線控制彈幕縮放:

為了節(jié)省性能常空,你可以將幾組配置好參數(shù)的臨界阻尼曲線執(zhí)行了間隔0.0167s的打點(diǎn),并將這些點(diǎn)的數(shù)值存儲在一個靜態(tài)的數(shù)組中盖溺,彈幕軌道執(zhí)行時直接從固定的幾個數(shù)組中獲取響應(yīng)的數(shù)值,這樣在使每個POP軌道中的彈幕均以相同的規(guī)律運(yùn)行的同時铣缠,也不必去反復(fù)計算那些繁瑣的指數(shù)烘嘱,減少了普通POP動畫執(zhí)行時數(shù)值計算的大量性能消耗昆禽。

結(jié)語

蘋果工程師們?yōu)殚_發(fā)者們構(gòu)建和諧社會中處處透露著精妙,作為iOS工程師的我們透過簡單的幾個接口和代理窺伺其追求極致用戶體驗(yàn)背后付出的巨大努力蝇庭。如果能有重新再來的機(jī)會醉鳖,我相信你一定會毫不猶豫的選購一部iPhone13XSProPlusMax,亦或是成為一名在追求極致的道路上腳踏實(shí)地哮内,披荊斬棘的iOS工程師盗棵。


這篇文章寫得比較正規(guī),學(xué)會了使用markdown中的公式語法北发,是基于之前寫過的一篇簡陋版本改進(jìn)的(之前的公式寫得實(shí)在太爛了纹因,自己都看不下去hhh):UIScrollView滾動特性

文章中使用到的Demo和手工實(shí)現(xiàn)的CustomScrollView均可以在我的Github下載:Github

如果覺得還不錯的話幫忙給個Star吧!感謝琳拨!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞭恰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狱庇,更是在濱河造成了極大的恐慌惊畏,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件密任,死亡現(xiàn)場離奇詭異颜启,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浪讳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門缰盏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人驻债,你說我怎么就攤上這事乳规。” “怎么了合呐?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵暮的,是天一觀的道長。 經(jīng)常有香客問我淌实,道長冻辩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任拆祈,我火速辦了婚禮恨闪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘放坏。我一直安慰自己咙咽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布淤年。 她就那樣靜靜地躺著钧敞,像睡著了一般蜡豹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溉苛,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天镜廉,我揣著相機(jī)與錄音,去河邊找鬼愚战。 笑死娇唯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寂玲。 我是一名探鬼主播塔插,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼敢茁!你這毒婦竟也來了佑淀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤彰檬,失蹤者是張志新(化名)和其女友劉穎伸刃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逢倍,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捧颅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了较雕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碉哑。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖亮蒋,靈堂內(nèi)的尸體忽然破棺而出扣典,到底是詐尸還是另有隱情,我是刑警寧澤慎玖,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布贮尖,位于F島的核電站,受9級特大地震影響趁怔,放射性物質(zhì)發(fā)生泄漏湿硝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一润努、第九天 我趴在偏房一處隱蔽的房頂上張望关斜。 院中可真熱鬧,春花似錦铺浇、人聲如沸痢畜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丁稀。三九已至繁涂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間二驰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工秉沼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桶雀,地道東北人。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓唬复,卻偏偏與公主長得像矗积,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子敞咧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359