github源碼地址
效果展示
前言
去年因?yàn)轫?xiàng)目中有個(gè)切換學(xué)校的功能塑娇,要求以卡片浮動(dòng)效果展示,并且能夠無限循環(huán)滾動(dòng)。
之前找了個(gè)demo它是通過自定義view動(dòng)畫實(shí)現(xiàn)的淋样,卡片數(shù)量多的時(shí)候會(huì)比較卡頓作岖,所以研究了一下唆垃,自己造了一個(gè),現(xiàn)在把實(shí)現(xiàn)的思路分享一下~
好痘儡,開始正題~~~
技術(shù)調(diào)研
在實(shí)現(xiàn)之前辕万,首先我想到的是如何實(shí)現(xiàn)無限循環(huán)的問題,以及多數(shù)量時(shí)如何復(fù)用的問題沉删。關(guān)于view
的無限循環(huán)滾動(dòng)和復(fù)用問題渐尿,最經(jīng)典的就是輪播圖了,現(xiàn)在比較常用的兩種方法就是UIScrollView
或者UICollectionView
實(shí)現(xiàn)的矾瑰。
然后我決定選用UICollectionView
涡戳,因?yàn)槲覀冎恍枰o它提供數(shù)據(jù)源驅(qū)動(dòng),它自己就可以復(fù)用脯倚,實(shí)現(xiàn)應(yīng)該比較簡(jiǎn)單渔彰。并且UICollectionView
的layout
的靈活性非常強(qiáng),完全可以自定義出很多酷炫的效果推正。
橫向滾動(dòng)
設(shè)置滾動(dòng)方向?yàn)樗交型浚梅猪搶傩?默認(rèn)就是false),這樣就可以橫向流暢滑動(dòng)了
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
collectionView.isPagingEnabled = false // default NO
無限循環(huán)
定義數(shù)組imageArr
來存儲(chǔ)圖片作為數(shù)據(jù)源植榕,在每次滾動(dòng)結(jié)束后再沧,都重新定位到第一張。
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at:.centeredHorizontally, animated: false)
}
但是每次重定位的時(shí)候都會(huì)出現(xiàn)明顯的突兀效果尊残,所以我們可以把這個(gè)數(shù)組中的圖片復(fù)制100份炒瘸、200份、1000份寝衫。顷扩。。這樣就有足夠多的數(shù)據(jù)來供用戶滑動(dòng)期間來展示了慰毅。
但是直接存儲(chǔ)圖片的話隘截,這個(gè)數(shù)組肯定會(huì)占用很大的內(nèi)存,所以我們開辟一個(gè)新的數(shù)組indexArr
,來存儲(chǔ)imageArr
中圖片的下標(biāo)婶芭,因?yàn)橹皇谴鎯?chǔ)的是int东臀,所以占用的內(nèi)存是非常小的。
// 初始化數(shù)據(jù)源
imageArr = ["num_1", "num_2", "num_3", "num_4", "num_5"]
for _ in 0 ..< 100 {
for j in 0 ..< imageArr.count {
indexArr.append(j)
}
}
比如imageArr中是["pic1", "pic2", "pic3"]犀农,那么indexArr中就是[0,1,2,0,1,2,0,1,2...]惰赋,這樣就可以把數(shù)據(jù)源設(shè)置為indexArr。
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return indexArr.count
}
代碼初始時(shí)我們把collectionView定位到中間那組(第50組呵哨,假設(shè)共設(shè)置了100組)赁濒,每次滾動(dòng)結(jié)束后也再次定位到中間組的第N張(N:上次滑動(dòng)結(jié)束時(shí)的那張)。
// 定位到 第50組(中間那組)
override func viewDidLoad() {
super.viewDidLoad()
collectionView.scrollToItem(at: IndexPath(item: 50 * imageArr.count, section: 0) , at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
}
每次動(dòng)畫停止時(shí)仇穗,也重新定位到 第50組(中間那組) 模型
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
collectionView.scrollToItem(at: IndexPath(item: 50 * imageArr.count, section: 0) , at: UICollectionViewScrollPosition.centeredHorizontally, animated: false)
}
每張卡片(cell)的數(shù)據(jù)填充
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(CyclicCardCell.self), for: indexPath) as! CyclicCardCell
let index = indexArr[indexPath.row]
cell.index = index
cell.cardImgView.image = UIImage(named: imageArr[index])
cell.cardNameLabel.text = "奔跑吧流部,小蝸牛~"
return cell
}
滾動(dòng)動(dòng)畫的平滑過渡
在每次重定位的時(shí)候,雖然設(shè)置的是回到中間組的對(duì)應(yīng)下標(biāo)的那個(gè)cell纹坐,并且animated也是設(shè)置的false枝冀,但是依然可以看出動(dòng)畫有些不連貫、突兀耘子,顯得不流暢自然果漾。
這就需要我們自定義UICollectionViewFlowLayout,來重寫targetContentOffset方法谷誓,通過遍歷目標(biāo)區(qū)域中的cell绒障,來計(jì)算出距離中心點(diǎn)最近的cell,把它調(diào)整到中間捍歪,實(shí)現(xiàn)平緩流暢的滑動(dòng)結(jié)束的效果户辱。
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let targetRect = CGRect(x: proposedContentOffset.x, y: 0.0, width: self.collectionView!.bounds.size.width, height: self.collectionView!.bounds.size.height)
// 目標(biāo)區(qū)域中包含的cell
let attriArray = super.layoutAttributesForElements(in: targetRect)! as [UICollectionViewLayoutAttributes]
// collectionView落在屏幕中點(diǎn)的x坐標(biāo)
let horizontalCenterX = proposedContentOffset.x + (self.collectionView!.bounds.width / 2.0)
var offsetAdjustment = CGFloat(MAXFLOAT)
for layoutAttributes in attriArray {
let itemHorizontalCenterX = layoutAttributes.center.x
// 找出離中心點(diǎn)最近的
if(abs(itemHorizontalCenterX-horizontalCenterX) < abs(offsetAdjustment)){
offsetAdjustment = itemHorizontalCenterX-horizontalCenterX
}
}
//返回collectionView最終停留的位置
return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y)
}
卡片的縮放動(dòng)畫
在自定義的UICollectionViewFlowLayout中重寫layoutAttributesForElements方法,通過該方法糙臼,可以獲取到可視范圍內(nèi)的cell庐镐。
然后在cell的滑動(dòng)過程中,通過cell偏移的距離來進(jìn)行尺寸的縮放系數(shù)的設(shè)置变逃,處于最中心的系數(shù)為1必逆,則為原本的大小,隨著中心距離的偏移揽乱,系數(shù)會(huì)逐漸變小為0.98名眉,0.95,0.8...
因此卡片也會(huì)隨之變小凰棉,從而達(dá)到在滾動(dòng)過程中损拢,滾動(dòng)至中間的卡片最大,旁邊的變小的效果渊啰。
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let array = super.layoutAttributesForElements(in: rect)
var visibleRect = CGRect()
visibleRect.origin = self.collectionView!.contentOffset
visibleRect.size = self.collectionView!.bounds.size
for attributes in array!{
let distance = visibleRect.midX - attributes.center.x
let normalizedDistance = abs(distance/ActiveDistance)
let zoom = 1 - ScaleFactor * normalizedDistance
attributes.transform3D = CATransform3DMakeScale(1.0, zoom, 1.0)
attributes.zIndex = 1
// let alpha = 1 - normalizedDistance
// attributes.alpha = alpha
}
return array
}
OK探橱,至此主要的核心實(shí)現(xiàn)就完成了申屹,可能有些地方思路表述的可能不是太清楚绘证,大家可以去github上下載源碼看下隧膏,有不對(duì)的地方,歡迎指正~
9月28號(hào)更新:
因?yàn)檫@個(gè)文章是第一次寫的嚷那,所以之前表述的不大好胞枕,今天重新修改了一下,希望能對(duì)大家有所幫助??
剛寫了一個(gè)類似的重疊卡片滾動(dòng)的動(dòng)畫魏宽,有興趣的可以看下: