原文由ariok發(fā)表宠蚂,地址是implementing-the-twitter-ios-app-ui
效果圖如下:
原來作者的代碼會存在一個概現(xiàn)Bug:當(dāng)快速下拉時,個人頭像并不會立刻顯示在HeaderView上方童社,我已經(jīng)向作者提交了Pull requests
編譯過程中會發(fā)生錯誤肥矢,因為swift更新了,所以需要自己解決下錯誤叠洗。
翻譯的比較差甘改,因為不做閱讀理解好多年了。灭抑。十艾。重點在效果。腾节。忘嫉。
結(jié)構(gòu)描述
在編碼之前,我將對UI的結(jié)構(gòu)做一個簡單的介紹案腺。
打開Main.storyboard文件庆冕。在唯一的一個控制器view中,你可以發(fā)現(xiàn)兩個主要的對象劈榨。第一個是顯示Header的視圖访递,第二個是一個包含個人頭像(我們叫它Avatar)和其他與賬號相關(guān),比如用戶名同辣、follow按鈕的ScrollView拷姿。Sizer控件只是用來確認(rèn)ScrollView內(nèi)容是否能進(jìn)行垂直滾動。
就像你所看到的旱函,這個結(jié)構(gòu)非常簡單响巢。需要的注意的是,我將Header放在ScrollView外面棒妨,而不是把它和其他ScrollView子控件放在一起踪古。因為這樣做可以讓這個結(jié)構(gòu)具備更好的擴(kuò)展性。
開始編碼
如果你仔細(xì)看完動畫券腔,你會注意到你可以管理兩個不同的動作:
用戶下拉(當(dāng)ScrollView內(nèi)容已經(jīng)在屏幕的頂部時)
用戶上/下滾動
這個動作可以分解成四步:
2.1) 向上滾動伏穆,Header控件縮小直到它的尺寸和導(dǎo)航欄默認(rèn)尺寸相等,然后這個Header控件就會粘在屏幕的上方
2.2) 向上滾動颅眶, Avatar(頭像)逐漸變小
2.3) 當(dāng)Header控件和ScrollView的子控件重疊時蜈出,Avatar(頭像)在Header控件底部
2.4) 當(dāng)用戶名Label的頂部和Header控件底部重疊時,一個新的白色Label將會從Header控件的中底部顯示涛酗。并且Header控件的圖片變模糊铡原。
打開ViewController.swift偷厦,讓我們一步一步地實現(xiàn)這些功能
設(shè)置控制器
第一件需要去做的事是獲取ScrollView的offset信息。通過實現(xiàn)UIScrollViewDelegate協(xié)議的scrollViewDidScroll方法燕刻,我們可以很容易地做到這一點只泼。
一種最簡單的展示一個view上變化的方式是使用CoreAnimation三維變換,并且設(shè)置新值給layer.transform屬性卵洗。
這個關(guān)于CoreAnimation的教程可能會讓它變得簡便:
http://www.thinkandbuild.it/playing-around-with-core-graphics-core-animation-and-touch-events-part-1/.
以下是scrollViewDidScroll方法的第一部分:
let offset = scrollView.contentOffset.y
var avatarTransform = CATransform3DIdentity
var headerTransform = CATransform3DIdentity
我們可以在這個方法里面獲取當(dāng)前的豎直偏移请唱,并且初始化兩個將要在方法后面設(shè)置的轉(zhuǎn)換信息。
下拉
讓我們對下拉動作進(jìn)行處理:
if offset < 0 {
let headerScaleFactor:CGFloat = -(offset) / header.bounds.height
let headerSizevariation = ((header.bounds.height * (1.0 + headerScaleFactor)) - header.bounds.height)/2.0
headerTransform = CATransform3DTranslate(headerTransform, 0, headerSizevariation, 0)
headerTransform = CATransform3DScale(headerTransform, 1.0 + headerScaleFactor, 1.0 + headerScaleFactor, 0)
header.layer.transform = headerTransform
}
首先过蹂,我們檢查偏移量是否為負(fù)數(shù)十绑,即ScrollView是否已出現(xiàn)彈性區(qū)域。
剩下的代碼是一些簡單的數(shù)學(xué)運算酷勺。
這個Header控件需要放大來保持它的上邊緣和屏幕頂部相對固定本橙,并且這個圖片是從底部開始放大的。
總的來說脆诉,這個變換主要由縮放甚亭,然后轉(zhuǎn)化view的尺寸變化為到頂部的距離構(gòu)成。事實上击胜,你可以朝屏幕頂端移動ImageView圖層的中點并且進(jìn)行縮放來相同的效果亏狰。
使用一個屬性來對頭部縮放比例進(jìn)行計算。我們希望Header控件參照偏移量進(jìn)行適當(dāng)?shù)目s放偶摔。換種說法:當(dāng)偏移量為Header視圖控件的兩倍時暇唾,頭部縮放比例應(yīng)該設(shè)置為2.0。
我們需要處理的第二個動作是上下滾動啰挪。讓我們看看如何一步一步地完成主要視圖的變換信不。
Header(第一階段)
當(dāng)前的偏移量應(yīng)該大于0嘲叔。Header控件應(yīng)該根據(jù)以下的偏移量來進(jìn)行豎直移動亡呵,直到它到達(dá)指定高度(我們下面將會對Header模糊進(jìn)行講解)。
headerTransform = CATransform3DTranslate(headerTransform, 0, max(-offset_HeaderStop, -offset), 0)
這段代碼真的是很簡單硫戈。我們只需要設(shè)置Header控件偏移一個最小值锰什,Header控件將會在offset_HeaderStop這個點停止移動。
因為我比較懶丁逝,所以我寫死了一些數(shù)值汁胆,比如offset_HeaderStop。我們可以通過更加優(yōu)雅的方式霜幼,比如計算UI控件的位置來實現(xiàn)相同的效果嫩码。或許在下一次我會試試罪既。
AVATAR(頭像)
這個頭像(圖片)以和下拉相同的邏輯進(jìn)行縮放铸题,只是在這種情況下铡恕,圖片是和底部貼合而不是頂部。這段代碼和上面比較相似丢间,除了減小縮放的比例為1.4探熔。
let avatarScaleFactor = (min(offset_HeaderStop, offset)) / avatarImage.bounds.height / 1.4 // Slow down the animation
let avatarSizeVariation = ((avatarImage.bounds.height * (1.0 + avatarScaleFactor)) - avatarImage.bounds.height) / 2.0
avatarTransform = CATransform3DTranslate(avatarTransform, 0, avatarSizeVariation, 0)
avatarTransform = CATransform3DScale(avatarTransform, 1.0 - avatarScaleFactor, 1.0 - avatarScaleFactor, 0)
就像你看到的,當(dāng)Header控件停止變化時烘挫,我們通過min函數(shù)來停止對個人頭像的縮放(offset_HeaderStop)诀艰。
此時,我們根據(jù)當(dāng)前的偏移量來設(shè)置最頂層的圖層饮六。除非偏移量大于等于offset_HeaderStop其垄,否則頂部圖層始終是個人頭像。當(dāng)偏移量大于offset_HeaderStop卤橄,這個圖層就變成了Header控件捉捅。
if offset <= offset_HeaderStop {
if avatarImage.layer.zPosition < header.layer.zPosition{
header.layer.zPosition = 0
}
}else {
if avatarImage.layer.zPosition >= header.layer.zPosition{
header.layer.zPosition = 2
}
}
白色Label
以下是白色Label執(zhí)行動畫的代碼:
let labelTransform = CATransform3DMakeTranslation(0, max(-distance_W_LabelHeader, offset_B_LabelHeader - offset), 0)
headerLabel.layer.transform = labelTransform
這里介紹兩個新的變量:當(dāng)偏移量等于offset_B_LabelHeader時,這個黑色的用戶名Label剛好到達(dá)Header視圖的底部虽风。
distance_W_LabelHeader是Header控件的底部和Header中的白色Label中點的距離棒口。
這個轉(zhuǎn)換通過以下邏輯進(jìn)行計算:黑色Label一旦喝Header控件相交,白色Label就立即顯示辜膝,并且白色Label到達(dá)Header控件的中點時停止无牵。所以使用以下代碼來創(chuàng)建Y的偏移:
max(-distance_W_LabelHeader, offset_B_LabelHeader - offset)
模糊
最后一個效果是模糊Header控件。為了得到合適的解決方案厂抖,我使用了三個不同的庫...我還嘗試創(chuàng)建自己的OpenGL ES茎毁,但是實時更新模糊效果總是非常遲緩。
我了解到我可以只對模糊進(jìn)行一次計算忱辅,讓模糊和非模糊的圖片進(jìn)行重疊七蜘,并且改變透明度值。我很確定墙懂,這就是Twitter采用的方法橡卤。
在viewDidAppear中我們計算模糊的Header并且通過設(shè)置透明度為0來進(jìn)行隱藏。
headerBlurImageView = UIImageView(frame: header.bounds)
headerBlurImageView?.image = UIImage(named: "header_bg")?.blurredImageWithRadius(10, iterations: 20, tintColor: UIColor.clearColor())
headerBlurImageView?.contentMode = UIViewContentMode.ScaleAspectFill
headerBlurImageView?.alpha = 0.0
header.insertSubview(headerBlurImageView, belowSubview: headerLabel)
模糊的view可以使用FXBlurView得到损搬。
在scrollViewDidScroll方法中碧库,我們只需要根據(jù)偏移量來更新透明度就行了。
headerBlurImageView?.alpha = min (1.0, (offset - offset_B_LabelHeader)/distance_W_LabelHeader)
以上計算邏輯主要為:透明度最大值必須為1巧勤,模糊效果必須在黑色Label到達(dá)Header控件時出現(xiàn)嵌灰,在白色Label停止后停止加深模糊。