演示案例 - 關(guān)系星球
實(shí)現(xiàn)效果:
需求背景
單個(gè)星球會(huì)有0~15個(gè)關(guān)系用戶,每個(gè)用戶以星球中點(diǎn)為圓心環(huán)繞擺放褪猛,星球只展示一半网杆,并且能上下拖動(dòng)查看更多用戶,如圖所示:
實(shí)現(xiàn)方案
1. 坐標(biāo)計(jì)算
從設(shè)計(jì)圖可得知伊滋,半邊星球最多展示6個(gè)用戶碳却,一圈360°,半圈180°笑旺,也就是多個(gè)用戶依次相隔30°環(huán)繞星球中心擺放昼浦。
已知圓心、半徑筒主、每個(gè)用戶的弧度关噪,通過三角函數(shù)就可以計(jì)算出每個(gè)用戶的(初始)坐標(biāo)了:
let centerX: CGFloat = circlePoint.x + radius * cos(radian)
let centerY: CGFloat = circlePoint.y + radius * sin(radian)
2. 手指轉(zhuǎn)動(dòng)
現(xiàn)在每個(gè)用戶的位置都可以確定了,還需要通過手指進(jìn)行轉(zhuǎn)動(dòng)物舒。
既然是用手指進(jìn)行轉(zhuǎn)動(dòng)色洞,是不是加個(gè)UIPanGestureRecognizer
然后改變弧度就可以實(shí)現(xiàn)轉(zhuǎn)動(dòng)了呢?
是可以冠胯,只不過只能單純轉(zhuǎn)動(dòng)火诸,沒有任何慣性,效果很是生硬荠察,雖然也可以通過一些數(shù)學(xué)公式實(shí)現(xiàn)慣性效果置蜀,不過對(duì)于我來說過于復(fù)雜且不好控制,所以作罷悉盆。
那UIKit
里面有沒有這種慣性拖動(dòng)的控件呢盯荤?--- 很明顯,UIScrollView
自帶慣性效果焕盟,這能滿足我的需求秋秤。
首先,想要UIScrollView
能夠拖動(dòng)脚翘,就得設(shè)置一個(gè)比它自身Size
還要大的contentSize
灼卢,至于要設(shè)置多高的contentSize
(這里需求是垂直方向,因此只需要設(shè)置contentSize.height
即可)才合適呢来农?
由設(shè)計(jì)圖可得知鞋真,星球一圈最多12個(gè),半圈則是6個(gè)沃于,也就是說涩咖,contentSize.height
等于1個(gè)星球高度時(shí)可容納6個(gè)用戶海诲,等于2個(gè)星球高度可容納12個(gè)用戶,也就是剛好容納一圈用戶所需的內(nèi)容高度檩互。所以平均一個(gè)用戶占用內(nèi)容高度為planet.height / 6
特幔,很好,這樣不管有多少個(gè)用戶盾似,都可以動(dòng)態(tài)設(shè)置contentSize.height
了敬辣。
contentSize.height = (planet.height / 6) * CGFloat(peopleViews.count)
確定好contentSize.height
了,接下來該如何通過拖拽進(jìn)行轉(zhuǎn)動(dòng)呢零院?
既然已經(jīng)知道了容納一圈用戶所需的內(nèi)容高度溉跃,也知道了一個(gè)用戶的占用內(nèi)容高度和角度,那就可以根據(jù)當(dāng)前偏移量和容納一圈用戶的內(nèi)容高度告抄,算出轉(zhuǎn)動(dòng)百分比撰茎,有了這個(gè)轉(zhuǎn)動(dòng)百分比,去刷新所有用戶的當(dāng)前轉(zhuǎn)動(dòng)位置了打洼。
let radian360 = CGFloat.pi * 2
let radian90 = CGFloat.pi / 2
let singlePeopleRadian = radian360 / 12.0
let oneRoundContentHeight = planet.frame.height * 2
let offsetY = scrollView.contentOffset.y
let progress = offsetY / oneRoundContentHeight
peopleViews.forEach { peopleView in
let index = peopleView.tag
// 弧度
var radian: CGFloat = singlePeopleRadian * CGFloat(index) - radian90 // iOS的0°為水平位置龄糊,-90°為了回去會(huì)垂直位置
radian -= progress * radian360 // 逆時(shí)針轉(zhuǎn)動(dòng),相減
// 中點(diǎn)
let centerX: CGFloat = circlePoint.x + radius * cos(radian)
let centerY: CGFloat = circlePoint.y + radius * sin(radian)
// 加上offsetY是為了讓所有用戶轉(zhuǎn)動(dòng)時(shí)能保持在scrollView的顯示區(qū)域內(nèi)
peopleView.center = CGPoint(x: centerX, y: offsetY + centerY)
}
其他需求
有了這個(gè)轉(zhuǎn)動(dòng)百分比募疮,剩下的需求就很容易實(shí)現(xiàn)了炫惩,例如控制名字的漸變顯示、只顯示右半屏的用戶等阿浓,這些都是給定一個(gè)限值他嚷,然后根據(jù)百分比慢慢刷新的事情,這里就不贅述了芭毙。
優(yōu)化
- 復(fù)用機(jī)制【已實(shí)現(xiàn)】:從視覺上筋蓖,一個(gè)屏幕也就最多顯示6~7個(gè)關(guān)系用戶,那就可以參考
UITableView
的做法退敦,使用一個(gè)集合當(dāng)作關(guān)系用戶的緩存池粘咖,轉(zhuǎn)動(dòng)過程中,但轉(zhuǎn)出顯示范圍(半屏)就把該視圖丟進(jìn)緩存池侈百,然后下一個(gè)用戶即將顯示時(shí)則從緩存池里取出來瓮下。最多只需要?jiǎng)?chuàng)建8個(gè)關(guān)系用戶視圖,即可實(shí)現(xiàn)無數(shù)個(gè)用戶的轉(zhuǎn)動(dòng)效果钝域,大大減少CPU的計(jì)算量讽坏。
- 動(dòng)態(tài)插入/刪除【未實(shí)現(xiàn)】:如果用整體刷新的動(dòng)畫來進(jìn)行插入/刪除,更新只會(huì)以兩個(gè)用戶之間的直線軌跡進(jìn)行位移動(dòng)畫网梢,這不符合轉(zhuǎn)盤的形式震缭,需加入圓弧動(dòng)畫赂毯,目前暫未實(shí)現(xiàn)战虏,將日后提供拣宰。
Demo
下載地址:TurntableView-Demo
- ??提供了調(diào)試功能,以便更好理解烦感。