現(xiàn)在好多app(喜馬拉雅,新浪微博等)都有類似UIPageViewController的效果,實(shí)際開(kāi)發(fā)中我們要進(jìn)行特殊效果,需要自定義,我寫(xiě)了一個(gè)swift版本的.效果如下
點(diǎn)擊鏈接下載 github下載地址
- 多標(biāo)題,可以滑動(dòng)

- 標(biāo)題較少,上面不滾動(dòng),距離等分

這種效果有很多坑點(diǎn),比如,快速連續(xù)滑動(dòng)時(shí)候流暢性問(wèn)題,字體顏色漸變色效果,標(biāo)題盡量滑到居中位置,我要起始在非第一個(gè)控制器上等等.下面我把關(guān)鍵代碼貼出來(lái).
UI架構(gòu)
我用的是父子控制器, parentVc.addChildViewController(vc),子控制器的視圖放在父控制器的collectionView上顯示.默認(rèn)樣式我用了一個(gè)JDPageStyle類專門(mén)進(jìn)行處理.
標(biāo)題較多時(shí)用 style.isScrollEnable = true 來(lái)讓上面的標(biāo)題可以滾動(dòng),標(biāo)題較少時(shí)style.isScrollEnable = false上面距離等分.
上面用scrollView 下面用collectionView,互為對(duì)方的代理,對(duì)方代理傳過(guò)來(lái)后自己滑動(dòng),自己滑動(dòng)發(fā)送代理對(duì)方滾動(dòng),對(duì)方傳來(lái)滑動(dòng)消息自己滑動(dòng)時(shí),不發(fā)送代理.
fileprivate func setupUI() {
// 1.將childVc添加到父控制器中
for vc in childVcs {
parentVc.addChildViewController(vc)
}
// 2.初始化用于顯示子控制器View的View(UIScrollView/UICollectionView)
addSubview(collectionView)
}
//設(shè)置contentView&titleView關(guān)系
titleView?.delegate = contentView
contentView?.delegate = titleView
- 設(shè)置起始index時(shí)要用多線程,主線程異步,這樣初始化完畢后才執(zhí)行這個(gè)滾動(dòng)方法.
DispatchQueue.main.async {
let indexPa = IndexPath(item: self.startIndex, section: 0)
self.contentView?.collectionView.scrollToItem(at: indexPa, at: .centeredHorizontally, animated: false)
self.contentView?.delegate?.contentView((self.contentView)!, targetIndex: self.startIndex, progress: 1)
self.contentView?.delegate?.contentView( (self.contentView)!, endScroll: self.startIndex)
}
- 按鈕滾動(dòng)到盡量居中位置
標(biāo)簽滑動(dòng)中心位置大于屏幕一半時(shí)候,如果scrollView足夠長(zhǎng),scrollview滑動(dòng),使標(biāo)簽居中
var offsetX = newLabel.center.x - scrollView.bounds.width * 0.5
if offsetX < 0 {
offsetX = 0
}
//滑動(dòng)到后面,scrollview不能再滑動(dòng)時(shí)候,不滑動(dòng)
let maxOffset = scrollView.contentSize.width - bounds.width
if offsetX > maxOffset {
offsetX = maxOffset
}
scrollView.setContentOffset(CGPoint(x: offsetX, y: 0), animated: true)
下面控制器滑動(dòng)時(shí)候
在scrollViewDidScroll里面判斷左滑右滑,并判斷進(jìn)度,把信息傳遞給上面.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x == startOffsetX || isForbidDelegate { return }
// 1.定義目標(biāo)的index、進(jìn)度
var targetIndex : Int = 0
var progress : CGFloat = 0
// 2.判斷用戶是左滑還是右滑
if scrollView.contentOffset.x > startOffsetX { // 左滑 右移動(dòng)
targetIndex = Int(startOffsetX / scrollView.bounds.width) + 1
if targetIndex >= childVcs.count {
targetIndex = childVcs.count - 1
}
progress = (scrollView.contentOffset.x - startOffsetX) / scrollView.bounds.width
} else { // 右滑
targetIndex = Int(startOffsetX / scrollView.bounds.width) - 1
if targetIndex < 0 {
targetIndex = 0
}
progress = (startOffsetX - scrollView.contentOffset.x) / scrollView.bounds.width
}
// 3.將數(shù)據(jù)傳遞給titleView
delegate?.contentView(self, targetIndex: targetIndex, progress: progress)
}}
快速滑動(dòng)細(xì)節(jié)處理 如果開(kāi)始拖拽時(shí)候下面view一頁(yè)沒(méi)滑動(dòng)完畢,正在滑動(dòng),應(yīng)計(jì)算他應(yīng)該在哪里結(jié)束,讓它滑動(dòng)到它應(yīng)該結(jié)束的地方,以此為其實(shí)值,要不會(huì)有上面標(biāo)題底部條顯示錯(cuò)亂
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// 記錄開(kāi)始的位置
isForbidDelegate = false
if Int(collectionView.contentOffset.x) % Int(collectionView.bounds.width) != 0 {
let theNum = collectionView.contentOffset.x / collectionView.bounds.width
let res = self.numFormat(num: Float(theNum ), format: "0")
collectionView.scrollToItem(at: IndexPath(item: Int(res)!, section: 0), at: .left, animated: false)
startOffsetX = collectionView.bounds.width*CGFloat(Float(res)!)
delegate?.contentView(self, targetIndex: Int(res)!, progress: 1)
delegate?.contentView(self, endScroll: Int(res)!)
}else{
startOffsetX = scrollView.contentOffset.x
}
}
文字顏色漸變處理
文字顏色不能有透明度,要可以轉(zhuǎn)成純的rgb顏色才可以
在上面view里面接受到下面正在滑動(dòng)中的progress時(shí)候,根據(jù)新舊兩個(gè)index,取出兩個(gè)頭部label,設(shè)置顏色字體等
func contentView(_ contentView: JDContentView, targetIndex: Int, progress: CGFloat) {
if progress < 1 {
self.isUserInteractionEnabled = false
}else{
self.isUserInteractionEnabled = true
}
// 1.取出兩個(gè)Label
let oldLabel = titleLabels[currentIndex]
let newLabel = titleLabels[targetIndex]
// 2.漸變文字顏色
let selectRGB = getGRBValue(style.selectColor)
let normalRGB = getGRBValue(style.normalColor)
let deltaRGB = (selectRGB.0 - normalRGB.0, selectRGB.1 - normalRGB.1, selectRGB.2 - normalRGB.2)
oldLabel.textColor = UIColor(r: selectRGB.0 - deltaRGB.0 * progress, g: selectRGB.1 - deltaRGB.1 * progress, b: selectRGB.2 - deltaRGB.2 * progress)
newLabel.textColor = UIColor(r: normalRGB.0 + deltaRGB.0 * progress, g: normalRGB.1 + deltaRGB.1 * progress, b: normalRGB.2 + deltaRGB.2 * progress)
if progress > 0.5 {
oldLabel.font = style.titleFont
newLabel.font = style.selectedTitleFont
}else{
oldLabel.font = style.selectedTitleFont
newLabel.font = style.titleFont
}}
private func getGRBValue(_ color : UIColor) -> (CGFloat, CGFloat, CGFloat) {
guard let components = color.cgColor.components else {
fatalError("文字顏色請(qǐng)按照RGB方式設(shè)置 顏色不能有透明度")
}
return (components[0] * 255, components[1] * 255, components[2] * 255)
}